From 0a2834b3aba371b356026d0954912a813e756f4a Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Thu, 27 Feb 2025 15:53:38 +0100 Subject: [PATCH 01/12] feat(x/gov): add burnDepositNoThreshold parameter wwith 0.7 as default value --- proto/atomone/gov/v1/gov.proto | 3 + x/gov/keeper/msg_server_test.go | 84 ++++++++++ x/gov/simulation/genesis.go | 20 ++- x/gov/simulation/genesis_test.go | 14 +- x/gov/types/v1/gov.pb.go | 263 +++++++++++++++++++------------ x/gov/types/v1/params.go | 15 ++ 6 files changed, 286 insertions(+), 113 deletions(-) diff --git a/proto/atomone/gov/v1/gov.proto b/proto/atomone/gov/v1/gov.proto index b50e2ed9..4b3c8171 100644 --- a/proto/atomone/gov/v1/gov.proto +++ b/proto/atomone/gov/v1/gov.proto @@ -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"]; } diff --git a/x/gov/keeper/msg_server_test.go b/x/gov/keeper/msg_server_test.go index beb76ee3..049c8a9d 100644 --- a/x/gov/keeper/msg_server_test.go +++ b/x/gov/keeper/msg_server_test.go @@ -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 { @@ -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 { @@ -1205,6 +1233,62 @@ 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: "negative burnDepositNoThreshold", + input: func() *v1.MsgUpdateParams { + params1 := params + params1.BurnDepositNoThreshold = "-0.1" + + return &v1.MsgUpdateParams{ + Authority: authority, + Params: params1, + } + }, + expErr: true, + expErrMsg: "burnDepositNoThreshold must be positive", + }, + { + 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 { diff --git a/x/gov/simulation/genesis.go b/x/gov/simulation/genesis.go index 809fe372..913a2103 100644 --- a/x/gov/simulation/genesis.go +++ b/x/gov/simulation/genesis.go @@ -48,6 +48,7 @@ const ( QuorumTimeout = "quorum_timeout" MaxVotingPeriodExtension = "max_voting_period_extension" QuorumCheckCount = "quorum_check_count" + BurnDepositNoThreshold = "burn_deposit_no_threshold" ) // GenDepositParamsDepositPeriod returns randomized DepositParamsDepositPeriod @@ -85,13 +86,13 @@ func GenMinDepositRatio(r *rand.Rand) math.LegacyDec { return math.LegacyMustNewDecFromStr("0.01") } -// GenTallyParamsQuorum returns randomized TallyParamsQuorum +// GenTallyParamsQuorum returns randomized TallyParamsConstitutionQuorum func GenTallyParamsConstitutionalQuorum(r *rand.Rand, minDec sdk.Dec) math.LegacyDec { min := int(minDec.Mul(sdk.NewDec(1000)).RoundInt64()) return sdk.NewDecWithPrec(int64(simulation.RandIntBetween(r, min, 600)), 3) } -// GenTallyParamsThreshold returns randomized TallyParamsThreshold +// GenTallyParamsThreshold returns randomized TallyParamsConstitutionalThreshold func GenTallyParamsConstitutionalThreshold(r *rand.Rand, minDec sdk.Dec) math.LegacyDec { min := int(minDec.Mul(sdk.NewDec(1000)).RoundInt64()) return sdk.NewDecWithPrec(int64(simulation.RandIntBetween(r, min, 950)), 3) @@ -152,6 +153,11 @@ func GenDepositParamsMinInitialDepositTargetProposals(r *rand.Rand) uint64 { return uint64(simulation.RandIntBetween(r, 1, 100)) } +// GenBurnDepositNoThreshold returns a randomized BurnDepositNoThreshold between 0.5 and 0.95 +func GenBurnDepositNoThreshold(r *rand.Rand) math.LegacyDec { + return sdk.NewDecWithPrec(int64(simulation.RandIntBetween(r, 500, 950)), 3) +} + // RandomizedGenState generates a random GenesisState for gov func RandomizedGenState(simState *module.SimulationState) { startingProposalID := uint64(simState.Rand.Intn(100)) @@ -324,6 +330,12 @@ func RandomizedGenState(simState *module.SimulationState) { }, ) + var burnDepositNoThreshold sdk.Dec + simState.AppParams.GetOrGenerate( + simState.Cdc, BurnDepositNoThreshold, &burnDepositNoThreshold, simState.Rand, + func(r *rand.Rand) { burnDepositNoThreshold = GenBurnDepositNoThreshold(r) }, + ) + govGenesis := v1.NewGenesisState( startingProposalID, v1.NewParams(depositPeriod, votingPeriod, quorum.String(), threshold.String(), amendmentsQuorum.String(), @@ -333,7 +345,9 @@ func RandomizedGenState(simState *module.SimulationState) { minDepositSensitivityTargetDistance, minDepositIncreaseRatio.String(), minDepositDecreaseRatio.String(), targetActiveProposals, minInitialDepositFloor, minInitialDepositUpdatePeriod, minInitialDepositSensitivityTargetDistance, minInitialDepositIncreaseRatio.String(), - minInitialDepositDecreaseRatio.String(), minInitialDepositTargetProposals), + minInitialDepositDecreaseRatio.String(), minInitialDepositTargetProposals, + burnDepositNoThreshold.String(), + ), ) bz, err := json.MarshalIndent(&govGenesis, "", " ") diff --git a/x/gov/simulation/genesis_test.go b/x/gov/simulation/genesis_test.go index d9c0c6b7..00893b76 100644 --- a/x/gov/simulation/genesis_test.go +++ b/x/gov/simulation/genesis_test.go @@ -46,12 +46,13 @@ func TestRandomizedGenState(t *testing.T) { simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &govGenesis) const ( - tallyQuorum = "0.294000000000000000" - tallyThreshold = "0.611000000000000000" - amendmentQuorum = "0.568000000000000000" - amendmentThreshold = "0.933000000000000000" - lawQuorum = "0.540000000000000000" - lawThreshold = "0.931000000000000000" + tallyQuorum = "0.362000000000000000" + tallyThreshold = "0.639000000000000000" + amendmentQuorum = "0.579000000000000000" + amendmentThreshold = "0.895000000000000000" + lawQuorum = "0.552000000000000000" + lawThreshold = "0.816000000000000000" + burnDepositNoThreshold = "0.716000000000000000" ) var ( @@ -72,6 +73,7 @@ func TestRandomizedGenState(t *testing.T) { require.Equal(t, "26h19m52s", govGenesis.Params.QuorumTimeout.String()) require.Equal(t, "120h29m51s", govGenesis.Params.MaxVotingPeriodExtension.String()) require.Equal(t, uint64(17), govGenesis.Params.QuorumCheckCount) + require.Equal(t, burnDepositNoThreshold, govGenesis.Params.BurnDepositNoThreshold) require.Equal(t, uint64(0x28), govGenesis.StartingProposalId) require.Equal(t, []*v1.Deposit{}, govGenesis.Deposits) require.Equal(t, []*v1.Vote{}, govGenesis.Votes) diff --git a/x/gov/types/v1/gov.pb.go b/x/gov/types/v1/gov.pb.go index 723329dc..0643cd67 100644 --- a/x/gov/types/v1/gov.pb.go +++ b/x/gov/types/v1/gov.pb.go @@ -1101,6 +1101,8 @@ type Params struct { QuorumCheckCount uint64 `protobuf:"varint,22,opt,name=quorum_check_count,json=quorumCheckCount,proto3" json:"quorum_check_count,omitempty"` MinDepositThrottler *MinDepositThrottler `protobuf:"bytes,23,opt,name=min_deposit_throttler,json=minDepositThrottler,proto3" json:"min_deposit_throttler,omitempty"` MinInitialDepositThrottler *MinInitialDepositThrottler `protobuf:"bytes,24,opt,name=min_initial_deposit_throttler,json=minInitialDepositThrottler,proto3" json:"min_initial_deposit_throttler,omitempty"` + // Minimum proportion of No Votes for a proposal deposit to be burnt. + BurnDepositNoThreshold string `protobuf:"bytes,25,opt,name=burn_deposit_no_threshold,json=burnDepositNoThreshold,proto3" json:"burn_deposit_no_threshold,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -1264,6 +1266,13 @@ func (m *Params) GetMinInitialDepositThrottler() *MinInitialDepositThrottler { return nil } +func (m *Params) GetBurnDepositNoThreshold() string { + if m != nil { + return m.BurnDepositNoThreshold + } + return "" +} + func init() { proto.RegisterEnum("atomone.gov.v1.VoteOption", VoteOption_name, VoteOption_value) proto.RegisterEnum("atomone.gov.v1.ProposalStatus", ProposalStatus_name, ProposalStatus_value) @@ -1285,114 +1294,115 @@ func init() { func init() { proto.RegisterFile("atomone/gov/v1/gov.proto", fileDescriptor_ecf0f9950ff6986c) } var fileDescriptor_ecf0f9950ff6986c = []byte{ - // 1698 bytes of a gzipped FileDescriptorProto + // 1722 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x58, 0xcd, 0x6f, 0x1b, 0xc7, 0x15, 0xd7, 0x8a, 0x1f, 0x92, 0x1e, 0x45, 0x6a, 0x35, 0x92, 0xed, 0x15, 0x15, 0x51, 0x2e, 0x5b, 0x04, 0x8e, 0x6b, 0x91, 0x95, 0x9d, 0xfa, 0x10, 0x04, 0x05, 0x28, 0x91, 0x76, 0xe9, 0xda, 0x22, 0xb3, 0x64, 0x94, 0xa6, 0x87, 0x2e, 0x86, 0xdc, 0x31, 0xb5, 0x08, 0x77, 0x87, 0xd9, 0x99, 0xa5, - 0xc5, 0x6b, 0x4e, 0x3d, 0xe6, 0x58, 0xf4, 0xd4, 0x63, 0x8f, 0x3d, 0x04, 0xe8, 0x1f, 0x50, 0x14, - 0xc8, 0xa9, 0x08, 0x72, 0x6a, 0x2f, 0x6e, 0x21, 0x1f, 0x0a, 0xe4, 0xde, 0x7b, 0xb1, 0x33, 0xb3, - 0xfc, 0xd2, 0x0a, 0xa2, 0x8c, 0x16, 0x68, 0x2e, 0x09, 0x77, 0xde, 0xef, 0xf7, 0xde, 0x9b, 0xf7, - 0x39, 0x16, 0x18, 0x98, 0x53, 0x97, 0x7a, 0xa4, 0xdc, 0xa3, 0xc3, 0xf2, 0xf0, 0x30, 0xfc, 0x5f, - 0x69, 0xe0, 0x53, 0x4e, 0x51, 0x4e, 0x49, 0x4a, 0xe1, 0xd1, 0xf0, 0x30, 0x5f, 0xe8, 0x52, 0xe6, - 0x52, 0x56, 0xee, 0x60, 0x46, 0xca, 0xc3, 0xc3, 0x0e, 0xe1, 0xf8, 0xb0, 0xdc, 0xa5, 0x8e, 0x27, - 0xf1, 0xf9, 0xed, 0x1e, 0xed, 0x51, 0xf1, 0xb3, 0x1c, 0xfe, 0x52, 0xa7, 0xfb, 0x3d, 0x4a, 0x7b, - 0x7d, 0x52, 0x16, 0x5f, 0x9d, 0xe0, 0x65, 0x99, 0x3b, 0x2e, 0x61, 0x1c, 0xbb, 0x03, 0x05, 0xd8, - 0x99, 0x07, 0x60, 0x6f, 0xa4, 0x44, 0x85, 0x79, 0x91, 0x1d, 0xf8, 0x98, 0x3b, 0x34, 0xb2, 0xb8, - 0x23, 0x3d, 0xb2, 0xa4, 0x51, 0xf9, 0xa1, 0x44, 0x9b, 0xd8, 0x75, 0x3c, 0x5a, 0x16, 0xff, 0x95, - 0x47, 0xc5, 0x01, 0xa0, 0x4f, 0x88, 0xd3, 0x3b, 0xe3, 0xc4, 0x3e, 0xa5, 0x9c, 0x34, 0x06, 0xa1, - 0x26, 0xf4, 0x10, 0xd2, 0x54, 0xfc, 0x32, 0xb4, 0xbb, 0xda, 0xbd, 0xdc, 0xc3, 0x7c, 0x69, 0xf6, - 0xda, 0xa5, 0x09, 0xd6, 0x54, 0x48, 0xf4, 0x2e, 0xa4, 0x5f, 0x09, 0x4d, 0xc6, 0xf2, 0x5d, 0xed, - 0xde, 0xda, 0x51, 0xee, 0xdb, 0xaf, 0x0e, 0x40, 0x99, 0xaf, 0x92, 0xae, 0xa9, 0xa4, 0xc5, 0xdf, - 0x6b, 0xb0, 0x52, 0x25, 0x03, 0xca, 0x1c, 0x8e, 0xf6, 0x21, 0x33, 0xf0, 0xe9, 0x80, 0x32, 0xdc, - 0xb7, 0x1c, 0x5b, 0x18, 0x4b, 0x9a, 0x10, 0x1d, 0xd5, 0x6d, 0xf4, 0x18, 0xd6, 0x6c, 0x89, 0xa5, - 0xbe, 0xd2, 0x6b, 0x7c, 0xfb, 0xd5, 0xc1, 0xb6, 0xd2, 0x5b, 0xb1, 0x6d, 0x9f, 0x30, 0xd6, 0xe2, - 0xbe, 0xe3, 0xf5, 0xcc, 0x09, 0x14, 0x7d, 0x08, 0x69, 0xec, 0xd2, 0xc0, 0xe3, 0x46, 0xe2, 0x6e, - 0xe2, 0x5e, 0xe6, 0xe1, 0x4e, 0x49, 0x31, 0xc2, 0x3c, 0x95, 0x54, 0x9e, 0x4a, 0xc7, 0xd4, 0xf1, - 0x8e, 0xd6, 0xbe, 0x7e, 0xbd, 0xbf, 0xf4, 0x87, 0x7f, 0xfd, 0xf1, 0xbe, 0x66, 0x2a, 0x4e, 0xf1, - 0x0b, 0x0d, 0x72, 0xcf, 0x31, 0xe3, 0x2f, 0x1c, 0x2f, 0xf2, 0xf4, 0x03, 0x48, 0x0d, 0x71, 0x3f, - 0x20, 0x86, 0x76, 0x03, 0x7d, 0x92, 0x82, 0xde, 0x87, 0x64, 0x98, 0x5f, 0xe1, 0x7f, 0xe6, 0x61, - 0xbe, 0x24, 0x13, 0x58, 0x8a, 0x12, 0x58, 0x6a, 0x47, 0xc9, 0x3f, 0x4a, 0x7e, 0xf9, 0x8f, 0x7d, - 0xcd, 0x14, 0xe8, 0xe2, 0x9f, 0x53, 0xb0, 0xda, 0x54, 0x91, 0x40, 0x39, 0x58, 0x1e, 0xc7, 0x67, - 0xd9, 0xb1, 0xd1, 0x4f, 0x60, 0xd5, 0x25, 0x8c, 0xe1, 0x1e, 0x61, 0xc6, 0xb2, 0xf0, 0x68, 0xfb, - 0x92, 0xda, 0x8a, 0x37, 0x32, 0xc7, 0x28, 0xf4, 0x18, 0xd2, 0x8c, 0x63, 0x1e, 0x30, 0x23, 0x21, - 0x52, 0x5a, 0x98, 0x4f, 0x69, 0x64, 0xab, 0x25, 0x50, 0xa6, 0x42, 0xa3, 0x3a, 0xa0, 0x97, 0x8e, - 0x87, 0xfb, 0x16, 0xc7, 0xfd, 0xfe, 0xc8, 0xf2, 0x09, 0x0b, 0xfa, 0xdc, 0x48, 0x8a, 0xab, 0xec, - 0xce, 0xeb, 0x68, 0x87, 0x18, 0x53, 0x40, 0x4c, 0x5d, 0xd0, 0xa6, 0x4e, 0x50, 0x05, 0x32, 0x2c, - 0xe8, 0xb8, 0x0e, 0xb7, 0x44, 0x38, 0x52, 0x0b, 0x86, 0x03, 0x24, 0x29, 0x3c, 0x46, 0xcf, 0x40, - 0x57, 0x49, 0xb6, 0x88, 0x67, 0x4b, 0x3d, 0xe9, 0x05, 0xf5, 0xe4, 0x14, 0xb3, 0xe6, 0xd9, 0x42, - 0x57, 0x1d, 0xb2, 0x9c, 0x72, 0xdc, 0xb7, 0xd4, 0xb9, 0xb1, 0x72, 0x83, 0xd4, 0xae, 0x0b, 0x6a, - 0x54, 0x1d, 0xcf, 0x61, 0x73, 0x48, 0xb9, 0xe3, 0xf5, 0x2c, 0xc6, 0xb1, 0xaf, 0xee, 0xb7, 0xba, - 0xa0, 0x5f, 0x1b, 0x92, 0xda, 0x0a, 0x99, 0xc2, 0xb1, 0x9f, 0x83, 0x3a, 0x9a, 0xdc, 0x71, 0x6d, - 0x41, 0x5d, 0x59, 0x49, 0x8c, 0xae, 0x98, 0x0f, 0xcb, 0x84, 0x63, 0x1b, 0x73, 0x6c, 0x40, 0xd8, - 0x3d, 0xe6, 0xf8, 0x1b, 0x6d, 0x43, 0x8a, 0x3b, 0xbc, 0x4f, 0x8c, 0x8c, 0x10, 0xc8, 0x0f, 0x64, - 0xc0, 0x0a, 0x0b, 0x5c, 0x17, 0xfb, 0x23, 0x63, 0x5d, 0x9c, 0x47, 0x9f, 0xe8, 0x7d, 0x58, 0x95, - 0x8d, 0x49, 0x7c, 0x23, 0x7b, 0x4d, 0x27, 0x8e, 0x91, 0xc5, 0xdf, 0x69, 0x90, 0x99, 0xae, 0x81, - 0x1f, 0xc3, 0xda, 0x88, 0x30, 0xab, 0x2b, 0x7a, 0x53, 0xbb, 0x34, 0x28, 0xea, 0x1e, 0x37, 0x57, - 0x47, 0x84, 0x1d, 0x87, 0x72, 0xf4, 0x08, 0xb2, 0xb8, 0xc3, 0x38, 0x76, 0x3c, 0x45, 0x58, 0x8e, - 0x25, 0xac, 0x2b, 0x90, 0x24, 0xbd, 0x07, 0xab, 0x1e, 0x55, 0xf8, 0x44, 0x2c, 0x7e, 0xc5, 0xa3, - 0x02, 0x5a, 0xfc, 0x93, 0x06, 0xc9, 0x70, 0x92, 0x5d, 0x3f, 0x87, 0x4a, 0x90, 0x1a, 0x52, 0x4e, - 0xae, 0x9f, 0x41, 0x12, 0x86, 0x3e, 0x84, 0x15, 0x39, 0x16, 0x99, 0x91, 0x14, 0x55, 0x55, 0x9c, - 0x6f, 0x95, 0xcb, 0x53, 0xd7, 0x8c, 0x28, 0x33, 0x69, 0x4b, 0xcd, 0xa6, 0xed, 0x59, 0x72, 0x35, - 0xa1, 0x27, 0x8b, 0x7f, 0xd1, 0xe0, 0xd6, 0x47, 0x01, 0xf5, 0x03, 0xf7, 0xf8, 0x8c, 0x74, 0x3f, - 0xfb, 0x28, 0x20, 0x01, 0xa9, 0x79, 0xdc, 0x1f, 0xa1, 0x26, 0x6c, 0x7d, 0x2e, 0x04, 0xa2, 0x70, - 0x68, 0xa0, 0x8a, 0x51, 0x5b, 0xb0, 0x80, 0x36, 0x25, 0xb9, 0x2d, 0xb9, 0xa2, 0x88, 0x1e, 0x00, - 0x52, 0x1a, 0xbb, 0xa1, 0xad, 0xa9, 0x54, 0x24, 0x4d, 0xfd, 0xf3, 0x89, 0x13, 0x32, 0xfc, 0x73, - 0x68, 0x66, 0xd9, 0xd4, 0x23, 0x22, 0x11, 0xb3, 0x68, 0x56, 0xa5, 0x1e, 0x29, 0xfe, 0x5d, 0x83, - 0xac, 0x6a, 0xa2, 0x26, 0xf6, 0xb1, 0xcb, 0xd0, 0xa7, 0x90, 0x71, 0x1d, 0x6f, 0xdc, 0x93, 0xd7, - 0x8e, 0xdb, 0xbd, 0xb0, 0x27, 0xbf, 0x7b, 0xbd, 0x7f, 0x6b, 0x8a, 0xf5, 0x80, 0xba, 0x0e, 0x27, - 0xee, 0x80, 0x8f, 0x4c, 0x70, 0x27, 0x33, 0xdc, 0x05, 0xe4, 0xe2, 0xf3, 0x08, 0x64, 0x0d, 0x88, - 0xef, 0x50, 0x5b, 0x4d, 0xe5, 0x9d, 0x4b, 0x91, 0xa9, 0xaa, 0xb5, 0x7a, 0xf4, 0xa3, 0xef, 0x5e, - 0xef, 0xbf, 0x73, 0x99, 0x38, 0x31, 0xf2, 0xdb, 0x30, 0x70, 0xba, 0x8b, 0xcf, 0xa3, 0x9b, 0x08, - 0x79, 0xb1, 0x0d, 0xeb, 0xa7, 0xa2, 0x1b, 0xd5, 0xcd, 0xaa, 0xa0, 0xba, 0x33, 0xb2, 0xac, 0x5d, - 0x67, 0x39, 0x29, 0x34, 0xaf, 0x4b, 0x96, 0xd2, 0xfa, 0xef, 0x65, 0xd5, 0x50, 0x4a, 0xeb, 0xbb, - 0x90, 0x96, 0x51, 0x8d, 0xe9, 0x26, 0xb1, 0x76, 0xa5, 0x14, 0x3d, 0x80, 0x35, 0x7e, 0xe6, 0x13, - 0x76, 0x46, 0xfb, 0xf6, 0x15, 0x1b, 0x7a, 0x02, 0x40, 0x26, 0xec, 0x75, 0xa9, 0xc7, 0xb8, 0xc3, - 0x83, 0xd0, 0x13, 0x0b, 0xbb, 0xc4, 0xb3, 0x5d, 0xe2, 0x71, 0x4b, 0x19, 0x4b, 0xc4, 0x6a, 0xd8, - 0x9d, 0x26, 0x55, 0x22, 0x8e, 0x2c, 0x54, 0xf4, 0x4b, 0xb8, 0x7b, 0x85, 0xce, 0x89, 0x63, 0xc9, - 0x58, 0xb5, 0x85, 0x58, 0xb5, 0xed, 0xb1, 0xb7, 0x07, 0x00, 0x7d, 0xfc, 0x2a, 0x72, 0x2d, 0x15, - 0x7f, 0xb9, 0x3e, 0x7e, 0xa5, 0x1c, 0x79, 0x04, 0xd9, 0x10, 0x3e, 0xb1, 0x9a, 0x8e, 0x65, 0xac, - 0xf7, 0xf1, 0xab, 0xb1, 0x8d, 0xe2, 0x6f, 0x12, 0xb0, 0x35, 0x79, 0x0f, 0xb4, 0xcf, 0x7c, 0xca, - 0x79, 0x9f, 0xf8, 0xa8, 0x06, 0x99, 0x97, 0x7d, 0x4a, 0x7d, 0xeb, 0xe6, 0xcf, 0x03, 0x10, 0xc4, - 0x53, 0xf1, 0x46, 0xa8, 0x42, 0x36, 0x18, 0xd8, 0x98, 0x93, 0x85, 0xcb, 0x52, 0x15, 0x87, 0x64, - 0xc9, 0xe2, 0x40, 0x8f, 0xe1, 0x0e, 0xc7, 0x7e, 0x8f, 0x70, 0x0b, 0x77, 0xb9, 0x33, 0x24, 0x56, - 0x34, 0xc2, 0x98, 0xea, 0xc0, 0x5b, 0x52, 0x5c, 0x11, 0xd2, 0x68, 0xe3, 0x33, 0xf4, 0x53, 0xc8, - 0x39, 0x5e, 0xd7, 0x27, 0x98, 0x11, 0x4b, 0xa8, 0xbf, 0x22, 0x11, 0xd9, 0x08, 0x65, 0x86, 0xa0, - 0x90, 0x66, 0x93, 0x19, 0x5a, 0x7c, 0xec, 0xb3, 0x11, 0x4a, 0xd2, 0x7e, 0x06, 0xbb, 0x8c, 0x78, - 0xcc, 0xe1, 0xce, 0xd0, 0xe1, 0x23, 0x4b, 0x79, 0x6c, 0x3b, 0x8c, 0x63, 0xaf, 0x2b, 0xf7, 0x79, - 0xd2, 0xdc, 0x99, 0x82, 0xb4, 0x05, 0xa2, 0xaa, 0x00, 0xc5, 0x2f, 0x12, 0x90, 0x7f, 0xe1, 0x78, - 0x75, 0xcf, 0xe1, 0xce, 0x78, 0x07, 0xff, 0x9f, 0x66, 0xe4, 0x3d, 0xd0, 0xd5, 0xfd, 0xe6, 0x53, - 0xb1, 0x21, 0xcf, 0xbf, 0xaf, 0x49, 0xb8, 0x58, 0x83, 0xb4, 0x1a, 0x41, 0x4f, 0x6f, 0x38, 0xb2, - 0x33, 0xe3, 0x80, 0x1b, 0xda, 0xcc, 0x80, 0x7e, 0xf1, 0x76, 0x03, 0x3a, 0x19, 0x3f, 0x80, 0x2f, - 0x0f, 0xdc, 0xc4, 0x5b, 0x0c, 0xdc, 0xa9, 0x01, 0x9b, 0x5c, 0x7c, 0xc0, 0xa6, 0xae, 0x1b, 0xb0, - 0xbf, 0x80, 0x9d, 0x30, 0x66, 0x8e, 0xac, 0xe1, 0xf1, 0x95, 0x65, 0x02, 0x57, 0x04, 0x5b, 0x9f, - 0x65, 0x1b, 0x9a, 0x79, 0xdb, 0x9d, 0xaf, 0x7a, 0x99, 0xcb, 0x7b, 0xa0, 0x77, 0x02, 0xdf, 0xb3, - 0xc2, 0xb7, 0x47, 0x34, 0x05, 0xc3, 0x27, 0xda, 0xaa, 0x99, 0x0b, 0xcf, 0xc3, 0x27, 0x86, 0x1a, - 0x7d, 0x15, 0xd8, 0x13, 0xc8, 0xf1, 0x6b, 0x67, 0x1c, 0x6b, 0x9f, 0x84, 0x6c, 0x23, 0x27, 0x68, - 0xf9, 0x10, 0x14, 0x55, 0x66, 0x14, 0x54, 0x89, 0x40, 0x1f, 0xc0, 0xe6, 0x54, 0xb6, 0x95, 0xc7, - 0x1b, 0xb1, 0xf7, 0xdd, 0x98, 0xe4, 0x56, 0x3a, 0x7a, 0xed, 0x5a, 0xd1, 0xff, 0x37, 0x6b, 0x65, - 0xf3, 0xbf, 0xb0, 0x56, 0xd0, 0x8d, 0xd7, 0xca, 0xd6, 0xf5, 0x6b, 0x05, 0x3d, 0x81, 0xdc, 0xec, - 0x73, 0xcd, 0xd8, 0x5e, 0xac, 0x48, 0xb3, 0x33, 0x0f, 0x35, 0xf4, 0x6b, 0xd8, 0x0d, 0x5b, 0x67, - 0xa6, 0xde, 0x2d, 0x72, 0xce, 0xc3, 0xfe, 0xa5, 0x9e, 0x71, 0x6b, 0x31, 0xa5, 0x86, 0x8b, 0xcf, - 0x4f, 0xa7, 0x8a, 0xbf, 0x16, 0x29, 0xb8, 0xe2, 0x11, 0x78, 0xfb, 0x8a, 0x47, 0xe0, 0x27, 0x30, - 0xfd, 0x1c, 0x0b, 0x43, 0x22, 0x67, 0xb3, 0x71, 0x47, 0xf8, 0xf1, 0xc3, 0xf9, 0xc7, 0x70, 0xcc, - 0x62, 0x35, 0xb7, 0xdc, 0x98, 0x6d, 0xeb, 0xc2, 0x5e, 0x5c, 0xdb, 0x4c, 0x0c, 0x18, 0xc2, 0xc0, - 0xfd, 0x18, 0x03, 0x57, 0xac, 0x0b, 0x33, 0xef, 0x5e, 0x29, 0xbb, 0xff, 0x19, 0xc0, 0xd4, 0x5f, - 0x45, 0x76, 0xe1, 0xce, 0x69, 0xa3, 0x5d, 0xb3, 0x1a, 0xcd, 0x76, 0xbd, 0x71, 0x62, 0x7d, 0x7c, - 0xd2, 0x6a, 0xd6, 0x8e, 0xeb, 0x4f, 0xea, 0xb5, 0xaa, 0xbe, 0x84, 0xb6, 0x60, 0x63, 0x5a, 0xf8, - 0x69, 0xad, 0xa5, 0x6b, 0xe8, 0x0e, 0x6c, 0x4d, 0x1f, 0x56, 0x8e, 0x5a, 0xed, 0x4a, 0xfd, 0x44, - 0x5f, 0x46, 0x08, 0x72, 0xd3, 0x82, 0x93, 0x86, 0x9e, 0xb8, 0xff, 0x57, 0x0d, 0x72, 0xb3, 0xff, - 0x08, 0x47, 0xfb, 0xb0, 0xdb, 0x34, 0x1b, 0xcd, 0x46, 0xab, 0xf2, 0xdc, 0x6a, 0xb5, 0x2b, 0xed, - 0x8f, 0x5b, 0x73, 0x56, 0x8b, 0x50, 0x98, 0x07, 0x54, 0x6b, 0xcd, 0x46, 0xab, 0xde, 0xb6, 0x9a, - 0x35, 0xb3, 0xde, 0xa8, 0xea, 0x1a, 0xfa, 0x01, 0xec, 0xcd, 0x63, 0x4e, 0x1b, 0xed, 0xfa, 0xc9, - 0xd3, 0x08, 0xb2, 0x8c, 0xf2, 0x70, 0x7b, 0x1e, 0xd2, 0xac, 0xb4, 0x5a, 0xb5, 0xaa, 0x9e, 0x40, - 0xef, 0x80, 0x31, 0x2f, 0x33, 0x6b, 0xcf, 0x6a, 0xc7, 0xed, 0x5a, 0x55, 0x4f, 0xc6, 0x31, 0x9f, - 0x54, 0xea, 0xcf, 0x6b, 0x55, 0x3d, 0x75, 0xf4, 0xf4, 0xeb, 0x8b, 0x82, 0xf6, 0xcd, 0x45, 0x41, - 0xfb, 0xe7, 0x45, 0x41, 0xfb, 0xf2, 0x4d, 0x61, 0xe9, 0x9b, 0x37, 0x85, 0xa5, 0xbf, 0xbd, 0x29, - 0x2c, 0xfd, 0xea, 0xa0, 0xe7, 0xf0, 0xb3, 0xa0, 0x53, 0xea, 0x52, 0xb7, 0xac, 0x32, 0x75, 0x70, - 0x16, 0x74, 0xa2, 0xdf, 0xe5, 0x73, 0xf1, 0x87, 0x37, 0x3e, 0x1a, 0x10, 0x56, 0x1e, 0x1e, 0x76, - 0xd2, 0xa2, 0x5e, 0x1f, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x62, 0x1f, 0xc8, 0x86, 0x97, 0x13, - 0x00, 0x00, + 0xc4, 0x6b, 0x4e, 0x3d, 0xe6, 0x58, 0xf4, 0xd4, 0x63, 0x2f, 0x05, 0x7a, 0x08, 0xd0, 0x3f, 0xa0, + 0x28, 0x90, 0x53, 0x11, 0xe4, 0xd4, 0x5e, 0xdc, 0xc2, 0x3e, 0x14, 0xc8, 0xbd, 0xf7, 0x62, 0x67, + 0x66, 0xf9, 0xa5, 0x15, 0x44, 0x19, 0x2d, 0xd0, 0x5e, 0x12, 0xee, 0xbc, 0xdf, 0xef, 0xbd, 0x37, + 0xef, 0x73, 0x2c, 0x30, 0x30, 0xa7, 0x2e, 0xf5, 0x48, 0xb9, 0x47, 0x87, 0xe5, 0xe1, 0x61, 0xf8, + 0xbf, 0xd2, 0xc0, 0xa7, 0x9c, 0xa2, 0x9c, 0x92, 0x94, 0xc2, 0xa3, 0xe1, 0x61, 0xbe, 0xd0, 0xa5, + 0xcc, 0xa5, 0xac, 0xdc, 0xc1, 0x8c, 0x94, 0x87, 0x87, 0x1d, 0xc2, 0xf1, 0x61, 0xb9, 0x4b, 0x1d, + 0x4f, 0xe2, 0xf3, 0xdb, 0x3d, 0xda, 0xa3, 0xe2, 0x67, 0x39, 0xfc, 0xa5, 0x4e, 0xf7, 0x7b, 0x94, + 0xf6, 0xfa, 0xa4, 0x2c, 0xbe, 0x3a, 0xc1, 0xcb, 0x32, 0x77, 0x5c, 0xc2, 0x38, 0x76, 0x07, 0x0a, + 0xb0, 0x33, 0x0f, 0xc0, 0xde, 0x48, 0x89, 0x0a, 0xf3, 0x22, 0x3b, 0xf0, 0x31, 0x77, 0x68, 0x64, + 0x71, 0x47, 0x7a, 0x64, 0x49, 0xa3, 0xf2, 0x43, 0x89, 0x36, 0xb1, 0xeb, 0x78, 0xb4, 0x2c, 0xfe, + 0x2b, 0x8f, 0x8a, 0x03, 0x40, 0x9f, 0x10, 0xa7, 0x77, 0xc6, 0x89, 0x7d, 0x4a, 0x39, 0x69, 0x0c, + 0x42, 0x4d, 0xe8, 0x21, 0xa4, 0xa9, 0xf8, 0x65, 0x68, 0x77, 0xb5, 0x7b, 0xb9, 0x87, 0xf9, 0xd2, + 0xec, 0xb5, 0x4b, 0x13, 0xac, 0xa9, 0x90, 0xe8, 0x5d, 0x48, 0x9f, 0x0b, 0x4d, 0xc6, 0xf2, 0x5d, + 0xed, 0xde, 0xda, 0x51, 0xee, 0xdb, 0xaf, 0x0e, 0x40, 0x99, 0xaf, 0x92, 0xae, 0xa9, 0xa4, 0xc5, + 0xdf, 0x6a, 0xb0, 0x52, 0x25, 0x03, 0xca, 0x1c, 0x8e, 0xf6, 0x21, 0x33, 0xf0, 0xe9, 0x80, 0x32, + 0xdc, 0xb7, 0x1c, 0x5b, 0x18, 0x4b, 0x9a, 0x10, 0x1d, 0xd5, 0x6d, 0xf4, 0x18, 0xd6, 0x6c, 0x89, + 0xa5, 0xbe, 0xd2, 0x6b, 0x7c, 0xfb, 0xd5, 0xc1, 0xb6, 0xd2, 0x5b, 0xb1, 0x6d, 0x9f, 0x30, 0xd6, + 0xe2, 0xbe, 0xe3, 0xf5, 0xcc, 0x09, 0x14, 0x7d, 0x08, 0x69, 0xec, 0xd2, 0xc0, 0xe3, 0x46, 0xe2, + 0x6e, 0xe2, 0x5e, 0xe6, 0xe1, 0x4e, 0x49, 0x31, 0xc2, 0x3c, 0x95, 0x54, 0x9e, 0x4a, 0xc7, 0xd4, + 0xf1, 0x8e, 0xd6, 0xbe, 0x7e, 0xb5, 0xbf, 0xf4, 0xbb, 0x7f, 0xfe, 0xe1, 0xbe, 0x66, 0x2a, 0x4e, + 0xf1, 0x0b, 0x0d, 0x72, 0xcf, 0x31, 0xe3, 0x2f, 0x1c, 0x2f, 0xf2, 0xf4, 0x03, 0x48, 0x0d, 0x71, + 0x3f, 0x20, 0x86, 0x76, 0x03, 0x7d, 0x92, 0x82, 0xde, 0x87, 0x64, 0x98, 0x5f, 0xe1, 0x7f, 0xe6, + 0x61, 0xbe, 0x24, 0x13, 0x58, 0x8a, 0x12, 0x58, 0x6a, 0x47, 0xc9, 0x3f, 0x4a, 0x7e, 0xf9, 0xf7, + 0x7d, 0xcd, 0x14, 0xe8, 0xe2, 0x9f, 0x52, 0xb0, 0xda, 0x54, 0x91, 0x40, 0x39, 0x58, 0x1e, 0xc7, + 0x67, 0xd9, 0xb1, 0xd1, 0x8f, 0x60, 0xd5, 0x25, 0x8c, 0xe1, 0x1e, 0x61, 0xc6, 0xb2, 0xf0, 0x68, + 0xfb, 0x92, 0xda, 0x8a, 0x37, 0x32, 0xc7, 0x28, 0xf4, 0x18, 0xd2, 0x8c, 0x63, 0x1e, 0x30, 0x23, + 0x21, 0x52, 0x5a, 0x98, 0x4f, 0x69, 0x64, 0xab, 0x25, 0x50, 0xa6, 0x42, 0xa3, 0x3a, 0xa0, 0x97, + 0x8e, 0x87, 0xfb, 0x16, 0xc7, 0xfd, 0xfe, 0xc8, 0xf2, 0x09, 0x0b, 0xfa, 0xdc, 0x48, 0x8a, 0xab, + 0xec, 0xce, 0xeb, 0x68, 0x87, 0x18, 0x53, 0x40, 0x4c, 0x5d, 0xd0, 0xa6, 0x4e, 0x50, 0x05, 0x32, + 0x2c, 0xe8, 0xb8, 0x0e, 0xb7, 0x44, 0x38, 0x52, 0x0b, 0x86, 0x03, 0x24, 0x29, 0x3c, 0x46, 0xcf, + 0x40, 0x57, 0x49, 0xb6, 0x88, 0x67, 0x4b, 0x3d, 0xe9, 0x05, 0xf5, 0xe4, 0x14, 0xb3, 0xe6, 0xd9, + 0x42, 0x57, 0x1d, 0xb2, 0x9c, 0x72, 0xdc, 0xb7, 0xd4, 0xb9, 0xb1, 0x72, 0x83, 0xd4, 0xae, 0x0b, + 0x6a, 0x54, 0x1d, 0xcf, 0x61, 0x73, 0x48, 0xb9, 0xe3, 0xf5, 0x2c, 0xc6, 0xb1, 0xaf, 0xee, 0xb7, + 0xba, 0xa0, 0x5f, 0x1b, 0x92, 0xda, 0x0a, 0x99, 0xc2, 0xb1, 0x9f, 0x82, 0x3a, 0x9a, 0xdc, 0x71, + 0x6d, 0x41, 0x5d, 0x59, 0x49, 0x8c, 0xae, 0x98, 0x0f, 0xcb, 0x84, 0x63, 0x1b, 0x73, 0x6c, 0x40, + 0xd8, 0x3d, 0xe6, 0xf8, 0x1b, 0x6d, 0x43, 0x8a, 0x3b, 0xbc, 0x4f, 0x8c, 0x8c, 0x10, 0xc8, 0x0f, + 0x64, 0xc0, 0x0a, 0x0b, 0x5c, 0x17, 0xfb, 0x23, 0x63, 0x5d, 0x9c, 0x47, 0x9f, 0xe8, 0x7d, 0x58, + 0x95, 0x8d, 0x49, 0x7c, 0x23, 0x7b, 0x4d, 0x27, 0x8e, 0x91, 0xc5, 0xdf, 0x68, 0x90, 0x99, 0xae, + 0x81, 0x1f, 0xc2, 0xda, 0x88, 0x30, 0xab, 0x2b, 0x7a, 0x53, 0xbb, 0x34, 0x28, 0xea, 0x1e, 0x37, + 0x57, 0x47, 0x84, 0x1d, 0x87, 0x72, 0xf4, 0x08, 0xb2, 0xb8, 0xc3, 0x38, 0x76, 0x3c, 0x45, 0x58, + 0x8e, 0x25, 0xac, 0x2b, 0x90, 0x24, 0xbd, 0x07, 0xab, 0x1e, 0x55, 0xf8, 0x44, 0x2c, 0x7e, 0xc5, + 0xa3, 0x02, 0x5a, 0xfc, 0xa3, 0x06, 0xc9, 0x70, 0x92, 0x5d, 0x3f, 0x87, 0x4a, 0x90, 0x1a, 0x52, + 0x4e, 0xae, 0x9f, 0x41, 0x12, 0x86, 0x3e, 0x84, 0x15, 0x39, 0x16, 0x99, 0x91, 0x14, 0x55, 0x55, + 0x9c, 0x6f, 0x95, 0xcb, 0x53, 0xd7, 0x8c, 0x28, 0x33, 0x69, 0x4b, 0xcd, 0xa6, 0xed, 0x59, 0x72, + 0x35, 0xa1, 0x27, 0x8b, 0x7f, 0xd6, 0xe0, 0xd6, 0x47, 0x01, 0xf5, 0x03, 0xf7, 0xf8, 0x8c, 0x74, + 0x3f, 0xfb, 0x28, 0x20, 0x01, 0xa9, 0x79, 0xdc, 0x1f, 0xa1, 0x26, 0x6c, 0x7d, 0x2e, 0x04, 0xa2, + 0x70, 0x68, 0xa0, 0x8a, 0x51, 0x5b, 0xb0, 0x80, 0x36, 0x25, 0xb9, 0x2d, 0xb9, 0xa2, 0x88, 0x1e, + 0x00, 0x52, 0x1a, 0xbb, 0xa1, 0xad, 0xa9, 0x54, 0x24, 0x4d, 0xfd, 0xf3, 0x89, 0x13, 0x32, 0xfc, + 0x73, 0x68, 0x66, 0xd9, 0xd4, 0x23, 0x22, 0x11, 0xb3, 0x68, 0x56, 0xa5, 0x1e, 0x29, 0xfe, 0x4d, + 0x83, 0xac, 0x6a, 0xa2, 0x26, 0xf6, 0xb1, 0xcb, 0xd0, 0xa7, 0x90, 0x71, 0x1d, 0x6f, 0xdc, 0x93, + 0xd7, 0x8e, 0xdb, 0xbd, 0xb0, 0x27, 0xbf, 0x7b, 0xb5, 0x7f, 0x6b, 0x8a, 0xf5, 0x80, 0xba, 0x0e, + 0x27, 0xee, 0x80, 0x8f, 0x4c, 0x70, 0x27, 0x33, 0xdc, 0x05, 0xe4, 0xe2, 0x8b, 0x08, 0x64, 0x0d, + 0x88, 0xef, 0x50, 0x5b, 0x4d, 0xe5, 0x9d, 0x4b, 0x91, 0xa9, 0xaa, 0xb5, 0x7a, 0xf4, 0x83, 0xef, + 0x5e, 0xed, 0xbf, 0x73, 0x99, 0x38, 0x31, 0xf2, 0xeb, 0x30, 0x70, 0xba, 0x8b, 0x2f, 0xa2, 0x9b, + 0x08, 0x79, 0xb1, 0x0d, 0xeb, 0xa7, 0xa2, 0x1b, 0xd5, 0xcd, 0xaa, 0xa0, 0xba, 0x33, 0xb2, 0xac, + 0x5d, 0x67, 0x39, 0x29, 0x34, 0xaf, 0x4b, 0x96, 0xd2, 0xfa, 0xaf, 0x65, 0xd5, 0x50, 0x4a, 0xeb, + 0xbb, 0x90, 0x96, 0x51, 0x8d, 0xe9, 0x26, 0xb1, 0x76, 0xa5, 0x14, 0x3d, 0x80, 0x35, 0x7e, 0xe6, + 0x13, 0x76, 0x46, 0xfb, 0xf6, 0x15, 0x1b, 0x7a, 0x02, 0x40, 0x26, 0xec, 0x75, 0xa9, 0xc7, 0xb8, + 0xc3, 0x83, 0xd0, 0x13, 0x0b, 0xbb, 0xc4, 0xb3, 0x5d, 0xe2, 0x71, 0x4b, 0x19, 0x4b, 0xc4, 0x6a, + 0xd8, 0x9d, 0x26, 0x55, 0x22, 0x8e, 0x2c, 0x54, 0xf4, 0x73, 0xb8, 0x7b, 0x85, 0xce, 0x89, 0x63, + 0xc9, 0x58, 0xb5, 0x85, 0x58, 0xb5, 0xed, 0xb1, 0xb7, 0x07, 0x00, 0x7d, 0x7c, 0x1e, 0xb9, 0x96, + 0x8a, 0xbf, 0x5c, 0x1f, 0x9f, 0x2b, 0x47, 0x1e, 0x41, 0x36, 0x84, 0x4f, 0xac, 0xa6, 0x63, 0x19, + 0xeb, 0x7d, 0x7c, 0x3e, 0xb6, 0x51, 0xfc, 0x55, 0x02, 0xb6, 0x26, 0xef, 0x81, 0xf6, 0x99, 0x4f, + 0x39, 0xef, 0x13, 0x1f, 0xd5, 0x20, 0xf3, 0xb2, 0x4f, 0xa9, 0x6f, 0xdd, 0xfc, 0x79, 0x00, 0x82, + 0x78, 0x2a, 0xde, 0x08, 0x55, 0xc8, 0x06, 0x03, 0x1b, 0x73, 0xb2, 0x70, 0x59, 0xaa, 0xe2, 0x90, + 0x2c, 0x59, 0x1c, 0xe8, 0x31, 0xdc, 0xe1, 0xd8, 0xef, 0x11, 0x6e, 0xe1, 0x2e, 0x77, 0x86, 0xc4, + 0x8a, 0x46, 0x18, 0x53, 0x1d, 0x78, 0x4b, 0x8a, 0x2b, 0x42, 0x1a, 0x6d, 0x7c, 0x86, 0x7e, 0x0c, + 0x39, 0xc7, 0xeb, 0xfa, 0x04, 0x33, 0x62, 0x09, 0xf5, 0x57, 0x24, 0x22, 0x1b, 0xa1, 0xcc, 0x10, + 0x14, 0xd2, 0x6c, 0x32, 0x43, 0x8b, 0x8f, 0x7d, 0x36, 0x42, 0x49, 0xda, 0x4f, 0x60, 0x97, 0x11, + 0x8f, 0x39, 0xdc, 0x19, 0x3a, 0x7c, 0x64, 0x29, 0x8f, 0x6d, 0x87, 0x71, 0xec, 0x75, 0xe5, 0x3e, + 0x4f, 0x9a, 0x3b, 0x53, 0x90, 0xb6, 0x40, 0x54, 0x15, 0xa0, 0xf8, 0x45, 0x02, 0xf2, 0x2f, 0x1c, + 0xaf, 0xee, 0x39, 0xdc, 0x19, 0xef, 0xe0, 0xff, 0xd1, 0x8c, 0xbc, 0x07, 0xba, 0xba, 0xdf, 0x7c, + 0x2a, 0x36, 0xe4, 0xf9, 0xff, 0x6b, 0x12, 0x7e, 0x0f, 0x90, 0x56, 0x23, 0xe8, 0xe9, 0x0d, 0x47, + 0x76, 0x66, 0x1c, 0x70, 0x43, 0x9b, 0x19, 0xd0, 0x2f, 0xde, 0x6e, 0x40, 0x27, 0xe3, 0x07, 0xf0, + 0xe5, 0x81, 0x9b, 0x78, 0x8b, 0x81, 0x3b, 0x35, 0x60, 0x93, 0x8b, 0x0f, 0xd8, 0xd4, 0x75, 0x03, + 0xf6, 0x67, 0xb0, 0x13, 0xc6, 0xcc, 0x91, 0x35, 0x3c, 0xbe, 0xb2, 0x4c, 0xe0, 0x8a, 0x60, 0xeb, + 0xb3, 0x6c, 0x43, 0x33, 0x6f, 0xbb, 0xf3, 0x55, 0x2f, 0x73, 0x79, 0x0f, 0xf4, 0x4e, 0xe0, 0x7b, + 0x56, 0xf8, 0xf6, 0x88, 0xa6, 0x60, 0xf8, 0x44, 0x5b, 0x35, 0x73, 0xe1, 0x79, 0xf8, 0xc4, 0x50, + 0xa3, 0xaf, 0x02, 0x7b, 0x02, 0x39, 0x7e, 0xed, 0x8c, 0x63, 0xed, 0x93, 0x90, 0x6d, 0xe4, 0x04, + 0x2d, 0x1f, 0x82, 0xa2, 0xca, 0x8c, 0x82, 0x2a, 0x11, 0xe8, 0x03, 0xd8, 0x9c, 0xca, 0xb6, 0xf2, + 0x78, 0x23, 0xf6, 0xbe, 0x1b, 0x93, 0xdc, 0x4a, 0x47, 0xaf, 0x5d, 0x2b, 0xfa, 0x7f, 0x67, 0xad, + 0x6c, 0xfe, 0x07, 0xd6, 0x0a, 0xba, 0xf1, 0x5a, 0xd9, 0xba, 0x7e, 0xad, 0xa0, 0x27, 0x90, 0x9b, + 0x7d, 0xae, 0x19, 0xdb, 0x8b, 0x15, 0x69, 0x76, 0xe6, 0xa1, 0x86, 0x7e, 0x09, 0xbb, 0x61, 0xeb, + 0xcc, 0xd4, 0xbb, 0x45, 0x2e, 0x78, 0xd8, 0xbf, 0xd4, 0x33, 0x6e, 0x2d, 0xa6, 0xd4, 0x70, 0xf1, + 0xc5, 0xe9, 0x54, 0xf1, 0xd7, 0x22, 0x05, 0x57, 0x3c, 0x02, 0x6f, 0x5f, 0xf1, 0x08, 0xfc, 0x04, + 0xa6, 0x9f, 0x63, 0x61, 0x48, 0xe4, 0x6c, 0x36, 0xee, 0x08, 0x3f, 0xbe, 0x3f, 0xff, 0x18, 0x8e, + 0x59, 0xac, 0xe6, 0x96, 0x1b, 0xb3, 0x6d, 0x5d, 0xd8, 0x8b, 0x6b, 0x9b, 0x89, 0x01, 0x43, 0x18, + 0xb8, 0x1f, 0x63, 0xe0, 0x8a, 0x75, 0x61, 0xe6, 0xdd, 0xab, 0x57, 0x49, 0x1d, 0x76, 0x44, 0xbb, + 0x44, 0x76, 0x3c, 0x3a, 0x95, 0xde, 0x9d, 0xd8, 0xf4, 0xde, 0x0e, 0x09, 0x4a, 0xd1, 0x09, 0x1d, + 0x27, 0xfa, 0xfe, 0x67, 0x00, 0x53, 0x7f, 0x60, 0xd9, 0x85, 0x3b, 0xa7, 0x8d, 0x76, 0xcd, 0x6a, + 0x34, 0xdb, 0xf5, 0xc6, 0x89, 0xf5, 0xf1, 0x49, 0xab, 0x59, 0x3b, 0xae, 0x3f, 0xa9, 0xd7, 0xaa, + 0xfa, 0x12, 0xda, 0x82, 0x8d, 0x69, 0xe1, 0xa7, 0xb5, 0x96, 0xae, 0xa1, 0x3b, 0xb0, 0x35, 0x7d, + 0x58, 0x39, 0x6a, 0xb5, 0x2b, 0xf5, 0x13, 0x7d, 0x19, 0x21, 0xc8, 0x4d, 0x0b, 0x4e, 0x1a, 0x7a, + 0xe2, 0xfe, 0x5f, 0x34, 0xc8, 0xcd, 0xfe, 0x7b, 0x1e, 0xed, 0xc3, 0x6e, 0xd3, 0x6c, 0x34, 0x1b, + 0xad, 0xca, 0x73, 0xab, 0xd5, 0xae, 0xb4, 0x3f, 0x6e, 0xcd, 0x59, 0x2d, 0x42, 0x61, 0x1e, 0x50, + 0xad, 0x35, 0x1b, 0xad, 0x7a, 0xdb, 0x6a, 0xd6, 0xcc, 0x7a, 0xa3, 0xaa, 0x6b, 0xe8, 0x7b, 0xb0, + 0x37, 0x8f, 0x39, 0x6d, 0xb4, 0xeb, 0x27, 0x4f, 0x23, 0xc8, 0x32, 0xca, 0xc3, 0xed, 0x79, 0x48, + 0xb3, 0xd2, 0x6a, 0xd5, 0xaa, 0x7a, 0x02, 0xbd, 0x03, 0xc6, 0xbc, 0xcc, 0xac, 0x3d, 0xab, 0x1d, + 0xb7, 0x6b, 0x55, 0x3d, 0x19, 0xc7, 0x7c, 0x52, 0xa9, 0x3f, 0xaf, 0x55, 0xf5, 0xd4, 0xd1, 0xd3, + 0xaf, 0x5f, 0x17, 0xb4, 0x6f, 0x5e, 0x17, 0xb4, 0x7f, 0xbc, 0x2e, 0x68, 0x5f, 0xbe, 0x29, 0x2c, + 0x7d, 0xf3, 0xa6, 0xb0, 0xf4, 0xd7, 0x37, 0x85, 0xa5, 0x5f, 0x1c, 0xf4, 0x1c, 0x7e, 0x16, 0x74, + 0x4a, 0x5d, 0xea, 0x96, 0x55, 0xd2, 0x0f, 0xce, 0x82, 0x4e, 0xf4, 0xbb, 0x7c, 0x21, 0xfe, 0x86, + 0xc7, 0x47, 0x03, 0xc2, 0xca, 0xc3, 0xc3, 0x4e, 0x5a, 0x94, 0xfe, 0xa3, 0x7f, 0x07, 0x00, 0x00, + 0xff, 0xff, 0xb5, 0x70, 0xbf, 0x41, 0xe2, 0x13, 0x00, 0x00, } func (m *WeightedVoteOption) Marshal() (dAtA []byte, err error) { @@ -2117,6 +2127,15 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.BurnDepositNoThreshold) > 0 { + i -= len(m.BurnDepositNoThreshold) + copy(dAtA[i:], m.BurnDepositNoThreshold) + i = encodeVarintGov(dAtA, i, uint64(len(m.BurnDepositNoThreshold))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xca + } if m.MinInitialDepositThrottler != nil { { size, err := m.MinInitialDepositThrottler.MarshalToSizedBuffer(dAtA[:i]) @@ -2702,6 +2721,10 @@ func (m *Params) Size() (n int) { l = m.MinInitialDepositThrottler.Size() n += 2 + l + sovGov(uint64(l)) } + l = len(m.BurnDepositNoThreshold) + if l > 0 { + n += 2 + l + sovGov(uint64(l)) + } return n } @@ -5454,6 +5477,38 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 25: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BurnDepositNoThreshold", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BurnDepositNoThreshold = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGov(dAtA[iNdEx:]) diff --git a/x/gov/types/v1/params.go b/x/gov/types/v1/params.go index 4db865d2..952893f0 100644 --- a/x/gov/types/v1/params.go +++ b/x/gov/types/v1/params.go @@ -57,6 +57,7 @@ var ( DefaultMinInitialDepositIncreaseRatio = sdk.NewDecWithPrec(1, 2) DefaultMinInitialDepositDecreaseRatio = sdk.NewDecWithPrec(5, 3) DefaultTargetProposalsInDepositPeriod uint64 = 5 + DefaultBurnDepositNoThreshold = sdk.NewDecWithPrec(70, 2) ) // Deprecated: NewDepositParams creates a new DepositParams object @@ -94,6 +95,7 @@ func NewParams( minDepositIncreaseRatio, minDepositDecreaseRatio string, targetActiveProposals uint64, minInitialDepositFloor sdk.Coins, minInitialDepositUpdatePeriod time.Duration, minInitialDepositSensitivityTargetDistance uint64, minInitialDepositIncreaseRatio, minInitialDepositDecreaseRatio string, targetProposalsInDepositPeriod uint64, + burnDepositNoThreshold string, ) Params { return Params{ // MinDeposit: minDeposit, // Deprecated in favor of dynamic min deposit @@ -128,6 +130,7 @@ func NewParams( DecreaseRatio: minInitialDepositDecreaseRatio, TargetProposals: targetProposalsInDepositPeriod, }, + BurnDepositNoThreshold: burnDepositNoThreshold, } } @@ -162,6 +165,7 @@ func DefaultParams() Params { DefaultMinInitialDepositIncreaseRatio.String(), DefaultMinInitialDepositDecreaseRatio.String(), DefaultTargetProposalsInDepositPeriod, + DefaultBurnDepositNoThreshold.String(), ) } @@ -419,5 +423,16 @@ func (p Params) ValidateBasic() error { return fmt.Errorf("minimum initial deposit decrease ratio too large: %s", minInitialDepositDecreaseRatio) } + burnDepositNoThreshold, err := sdk.NewDecFromStr(p.BurnDepositNoThreshold) + if err != nil { + return fmt.Errorf("invalid burnDepositNoThreshold string: %w", err) + } + if !burnDepositNoThreshold.IsPositive() { + return fmt.Errorf("burnDepositNoThreshold must be positive: %s", burnDepositNoThreshold) + } + if burnDepositNoThreshold.GT(math.LegacyOneDec()) { + return fmt.Errorf("burnDepositNoThreshold too large: %s", burnDepositNoThreshold) + } + return nil } From 3daa96f7d5b81f979fdea5772e9eb41cd2776540 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Thu, 27 Feb 2025 17:23:31 +0100 Subject: [PATCH 02/12] feat(x/gov): burn deposit if no votes > burnDepositNoThreshold update default value to 0.8 --- x/gov/abci.go | 9 +++++- x/gov/keeper/grpc_query.go | 7 ++++- x/gov/keeper/tally.go | 37 ++++++++++++++++++------- x/gov/keeper/tally_test.go | 57 +++++++++++++++++++++++++++++--------- x/gov/types/v1/params.go | 2 +- 5 files changed, 86 insertions(+), 26 deletions(-) diff --git a/x/gov/abci.go b/x/gov/abci.go index 091e1f64..d3a69757 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -71,6 +71,8 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) { // check if proposal passed quorum quorum, err := keeper.HasReachedQuorum(ctx, proposal) if err != nil { + logger.Error("failed to check proposal quorum", "proposal", proposal.Id, + "title", proposal.Title, "err", err) return false } logMsg := "proposal did not pass quorum after timeout, but was removed from quorum check queue" @@ -139,7 +141,12 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) { keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal v1.Proposal) bool { var tagValue, logMsg string - passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) + passes, burnDeposits, tallyResults, err := keeper.Tally(ctx, proposal) + if err != nil { + logger.Error("failed to tally proposal", "proposal", proposal.Id, + "title", proposal.Title, "err", err) + return false + } if burnDeposits { keeper.DeleteAndBurnDeposits(ctx, proposal.Id) diff --git a/x/gov/keeper/grpc_query.go b/x/gov/keeper/grpc_query.go index 9335d4d8..006d662d 100644 --- a/x/gov/keeper/grpc_query.go +++ b/x/gov/keeper/grpc_query.go @@ -285,7 +285,12 @@ func (q Keeper) TallyResult(c context.Context, req *v1.QueryTallyResultRequest) default: // proposal is in voting period - _, _, tallyResult = q.Tally(ctx, proposal) + var err error + _, _, tallyResult, err = q.Tally(ctx, proposal) + if err != nil { + return nil, err + } + } return &v1.QueryTallyResultResponse{Tally: &tallyResult}, nil diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index 7b65fbaf..e7e2ae24 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -13,7 +13,7 @@ import ( // Tally iterates over the votes and updates the tally of a proposal based on the voting power of the // voters -func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult) { +func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult, err error) { // fetch all the bonded validators currValidators := keeper.getBondedValidatorsByAddress(ctx) totalVotingPower, results := keeper.tallyVotes(ctx, proposal, currValidators, true) @@ -24,30 +24,47 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, // If there is no staked coins, the proposal fails totalBonded := keeper.sk.TotalBondedTokens(ctx) if totalBonded.IsZero() { - return false, false, tallyResults + return false, false, tallyResults, nil } // If there is not enough quorum of votes, the proposal fails percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded)) quorum, threshold, err := keeper.getQuorumAndThreshold(ctx, proposal) if err != nil { - return false, false, tallyResults + return false, false, tallyResults, err } if percentVoting.LT(quorum) { - return false, params.BurnVoteQuorum, tallyResults + return false, params.BurnVoteQuorum, tallyResults, nil } + // Compute non-abstaining voting power, aka active voting power + activeVotingPower := totalVotingPower.Sub(results[v1.OptionAbstain]) + // If no one votes (everyone abstains), proposal fails - if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) { - return false, false, tallyResults + if activeVotingPower.IsZero() { + return false, false, tallyResults, nil + } + + // If more than `threshold` of non-abstaining voters vote Yes, proposal passes. + yesPercent := results[v1.OptionYes].Quo(activeVotingPower) + if yesPercent.GT(threshold) { + return true, false, tallyResults, nil } - if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) { - return true, false, tallyResults + // If more than `burnDepositNoThreshold` of non-abstaining voters vote No, + // proposal is rejected and deposit is burned. + burnDepositNoThreshold, err := sdk.NewDecFromStr(params.BurnDepositNoThreshold) + if err != nil { + return false, true, tallyResults, err + } + noPercent := results[v1.OptionNo].Quo(activeVotingPower) + if noPercent.GT(burnDepositNoThreshold) { + return false, true, tallyResults, nil } - // If more than 1/2 of non-abstaining voters vote No, proposal fails - return false, false, tallyResults + // If less than `burnDepositNoThreshold` of non-abstaining voters vote No, + // proposal is rejected but deposit is not burned. + return false, false, tallyResults, nil } // HasReachedQuorum returns whether or not a proposal has reached quorum diff --git a/x/gov/keeper/tally_test.go b/x/gov/keeper/tally_test.go index d1deed9d..f1edaefe 100644 --- a/x/gov/keeper/tally_test.go +++ b/x/gov/keeper/tally_test.go @@ -271,24 +271,29 @@ func TestTally(t *testing.T) { }, }, { - name: "quorum reached with yes<=.667: prop fails", + name: "quorum reached with yes<=.667: prop rejected", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) - s.validatorVote(s.valAddrs[2], v1.VoteOption_VOTE_OPTION_NO) - s.validatorVote(s.valAddrs[3], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[2], v1.VoteOption_VOTE_OPTION_YES) + s.validatorVote(s.valAddrs[3], v1.VoteOption_VOTE_OPTION_YES) + s.validatorVote(s.valAddrs[4], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[5], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[6], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[7], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[8], v1.VoteOption_VOTE_OPTION_ABSTAIN) }, proposalMsgs: TestProposal, expectedPass: false, expectedBurn: false, expectedTally: v1.TallyResult{ - YesCount: "2", - AbstainCount: "0", + YesCount: "4", + AbstainCount: "3", NoCount: "2", }, }, { - name: "quorum reached with yes>.667: prop succeeds", + name: "quorum reached with yes>.667: prop passes", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) @@ -296,18 +301,43 @@ func TestTally(t *testing.T) { s.validatorVote(s.valAddrs[3], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[4], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[5], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[6], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[7], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[8], v1.VoteOption_VOTE_OPTION_ABSTAIN) }, proposalMsgs: TestProposal, expectedPass: true, expectedBurn: false, expectedTally: v1.TallyResult{ YesCount: "5", - AbstainCount: "0", + AbstainCount: "3", NoCount: "1", }, }, { - name: "quorum reached thanks to abstain, yes>.667: prop succeeds", + name: "quorum reached with no>.7: prop rejected and deposit burned", + setup: func(s *tallyFixture) { + s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) + s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[2], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[3], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[4], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[5], v1.VoteOption_VOTE_OPTION_NO) + s.validatorVote(s.valAddrs[6], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[7], v1.VoteOption_VOTE_OPTION_ABSTAIN) + s.validatorVote(s.valAddrs[8], v1.VoteOption_VOTE_OPTION_ABSTAIN) + }, + proposalMsgs: TestProposal, + expectedPass: false, + expectedBurn: true, + expectedTally: v1.TallyResult{ + YesCount: "1", + AbstainCount: "3", + NoCount: "5", + }, + }, + { + name: "quorum reached thanks to abstain, yes>.667: prop passes", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) @@ -326,7 +356,7 @@ func TestTally(t *testing.T) { }, }, { - name: "amendment quorum not reached: prop fails", + name: "amendment quorum not reached: prop rejected", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) @@ -341,7 +371,7 @@ func TestTally(t *testing.T) { }, }, { - name: "amendment quorum reached and threshold not reached: prop fails", + name: "amendment quorum reached and threshold not reached: prop rejected", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) @@ -384,7 +414,7 @@ func TestTally(t *testing.T) { }, }, { - name: "law quorum not reached: prop fails", + name: "law quorum not reached: prop rejected", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[2], v1.VoteOption_VOTE_OPTION_NO) @@ -399,7 +429,7 @@ func TestTally(t *testing.T) { }, }, { - name: "law quorum reached and threshold not reached: prop fails", + name: "law quorum reached and threshold not reached: prop rejected", setup: func(s *tallyFixture) { s.validatorVote(s.valAddrs[0], v1.VoteOption_VOTE_OPTION_YES) s.validatorVote(s.valAddrs[1], v1.VoteOption_VOTE_OPTION_YES) @@ -460,8 +490,9 @@ func TestTally(t *testing.T) { tt.setup(s) } - pass, burn, tally := govKeeper.Tally(ctx, proposal) + pass, burn, tally, err := govKeeper.Tally(ctx, proposal) + require.NoError(t, err) assert.Equal(t, tt.expectedPass, pass, "wrong pass") assert.Equal(t, tt.expectedBurn, burn, "wrong burn") assert.Equal(t, tt.expectedTally, tally) diff --git a/x/gov/types/v1/params.go b/x/gov/types/v1/params.go index 952893f0..f5832063 100644 --- a/x/gov/types/v1/params.go +++ b/x/gov/types/v1/params.go @@ -57,7 +57,7 @@ var ( DefaultMinInitialDepositIncreaseRatio = sdk.NewDecWithPrec(1, 2) DefaultMinInitialDepositDecreaseRatio = sdk.NewDecWithPrec(5, 3) DefaultTargetProposalsInDepositPeriod uint64 = 5 - DefaultBurnDepositNoThreshold = sdk.NewDecWithPrec(70, 2) + DefaultBurnDepositNoThreshold = sdk.NewDecWithPrec(80, 2) ) // Deprecated: NewDepositParams creates a new DepositParams object From ab553f01c9ee187e08edd9d56811b4442e4f2a45 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Fri, 28 Feb 2025 16:53:13 +0100 Subject: [PATCH 03/12] test(x/gov): e2e for burnDepositNoThreshold --- tests/e2e/e2e_gov_test.go | 75 +++++++++++++++++++++++++++++++++++---- tests/e2e/genesis_test.go | 1 + 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index d14096fd..abbc1c05 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -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) @@ -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 not 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) @@ -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. @@ -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) @@ -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") @@ -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") @@ -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 { @@ -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) { diff --git a/tests/e2e/genesis_test.go b/tests/e2e/genesis_test.go index 78def00b..951512ff 100644 --- a/tests/e2e/genesis_test.go +++ b/tests/e2e/genesis_test.go @@ -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" From 6cfbbf8dad7fc0ccd8bfdfdce1f5eb2cf98f55c0 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Fri, 28 Feb 2025 17:55:08 +0100 Subject: [PATCH 04/12] fix comment --- tests/e2e/e2e_gov_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/e2e_gov_test.go b/tests/e2e/e2e_gov_test.go index abbc1c05..868bb50d 100644 --- a/tests/e2e/e2e_gov_test.go +++ b/tests/e2e/e2e_gov_test.go @@ -144,7 +144,7 @@ func (s *IntegrationTestSuite) testGovCommunityPoolSpend() { voteGovFlags := []string{strconv.Itoa(proposalCounter), "yes"} s.submitGovProposal(chainAAPIEndpoint, sender, proposalCounter, "CommunityPoolSpend", submitGovFlags, depositGovFlags, voteGovFlags, "vote", govtypesv1beta1.StatusPassed) - // Check that sender is not refunded with the proposal deposit + // Check that sender is refunded with the proposal deposit s.Require().Eventually( func() bool { afterSenderBalance, err := getSpecificBalance(chainAAPIEndpoint, sender, uatoneDenom) From cd862086458262ab2f36c35e1d42e4dd6b888e49 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Mon, 3 Mar 2025 10:19:36 +0100 Subject: [PATCH 05/12] docs(x/gov): update with burnNoThresholdDeposit --- x/gov/README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/x/gov/README.md b/x/gov/README.md index c89007ea..9aeac91a 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -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) @@ -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) @@ -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 burnt. + #### Weighted Votes [ADR-037](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-037-gov-split-vote.md) @@ -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. From 1b19c539641e7395df25173b43ecbed4bc489257 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Mon, 3 Mar 2025 14:33:57 +0100 Subject: [PATCH 06/12] chore(x/gov): migration to add BurnDepositNoThreshold param --- x/gov/migrations/v5/store.go | 2 ++ x/gov/migrations/v5/store_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/x/gov/migrations/v5/store.go b/x/gov/migrations/v5/store.go index 3ce8d29f..034371a9 100644 --- a/x/gov/migrations/v5/store.go +++ b/x/gov/migrations/v5/store.go @@ -11,6 +11,7 @@ import ( var ParamsKey = []byte{0x30} // Addition of the dynamic-deposit parameters. +// Addition of the burnDepositNoThreshold parameter. func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error { store := ctx.KVStore(storeKey) paramsBz := store.Get(ParamsKey) @@ -21,6 +22,7 @@ func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.Binar defaultParams := govv1.DefaultParams() params.MinDepositThrottler = defaultParams.MinDepositThrottler params.MinInitialDepositThrottler = defaultParams.MinInitialDepositThrottler + params.BurnDepositNoThreshold = defaultParams.BurnDepositNoThreshold bz, err := cdc.Marshal(¶ms) if err != nil { diff --git a/x/gov/migrations/v5/store_test.go b/x/gov/migrations/v5/store_test.go index fd571688..e37b6cbe 100644 --- a/x/gov/migrations/v5/store_test.go +++ b/x/gov/migrations/v5/store_test.go @@ -27,6 +27,7 @@ func TestMigrateStore(t *testing.T) { require.NotNil(t, params) require.Nil(t, params.MinDepositThrottler) require.Nil(t, params.MinInitialDepositThrottler) + require.Equal(t, "", params.BurnDepositNoThreshold) // Run migrations. err := v5.MigrateStore(ctx, govKey, cdc) @@ -38,4 +39,5 @@ func TestMigrateStore(t *testing.T) { require.NotNil(t, params) require.Equal(t, govv1.DefaultParams().MinDepositThrottler, params.MinDepositThrottler) require.Equal(t, govv1.DefaultParams().MinInitialDepositThrottler, params.MinInitialDepositThrottler) + require.Equal(t, govv1.DefaultParams().BurnDepositNoThreshold, params.BurnDepositNoThreshold) } From 07540f57bf3a9992c63368c61e3114c4c8e1bc7d Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Mon, 3 Mar 2025 14:50:52 +0100 Subject: [PATCH 07/12] fix test --- x/gov/keeper/tally.go | 2 +- x/gov/simulation/genesis_test.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index e7e2ae24..cc459149 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -55,7 +55,7 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, // proposal is rejected and deposit is burned. burnDepositNoThreshold, err := sdk.NewDecFromStr(params.BurnDepositNoThreshold) if err != nil { - return false, true, tallyResults, err + return false, false, tallyResults, err } noPercent := results[v1.OptionNo].Quo(activeVotingPower) if noPercent.GT(burnDepositNoThreshold) { diff --git a/x/gov/simulation/genesis_test.go b/x/gov/simulation/genesis_test.go index 00893b76..0345f2d5 100644 --- a/x/gov/simulation/genesis_test.go +++ b/x/gov/simulation/genesis_test.go @@ -46,13 +46,13 @@ func TestRandomizedGenState(t *testing.T) { simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &govGenesis) const ( - tallyQuorum = "0.362000000000000000" - tallyThreshold = "0.639000000000000000" - amendmentQuorum = "0.579000000000000000" - amendmentThreshold = "0.895000000000000000" - lawQuorum = "0.552000000000000000" - lawThreshold = "0.816000000000000000" - burnDepositNoThreshold = "0.716000000000000000" + tallyQuorum = "0.294000000000000000" + tallyThreshold = "0.611000000000000000" + amendmentQuorum = "0.568000000000000000" + amendmentThreshold = "0.933000000000000000" + lawQuorum = "0.540000000000000000" + lawThreshold = "0.931000000000000000" + burnDepositNoThreshold = "0.758000000000000000" ) var ( From 19e693e123171f26b6439dd462f830bc504724b7 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Thu, 6 Mar 2025 11:35:57 +0100 Subject: [PATCH 08/12] use MustNewDecFromStr --- x/gov/keeper/tally.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index cc459149..ba24ee49 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -53,10 +53,7 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, // If more than `burnDepositNoThreshold` of non-abstaining voters vote No, // proposal is rejected and deposit is burned. - burnDepositNoThreshold, err := sdk.NewDecFromStr(params.BurnDepositNoThreshold) - if err != nil { - return false, false, tallyResults, err - } + burnDepositNoThreshold := sdk.MustNewDecFromStr(params.BurnDepositNoThreshold) noPercent := results[v1.OptionNo].Quo(activeVotingPower) if noPercent.GT(burnDepositNoThreshold) { return false, true, tallyResults, nil From 658af5dc4a6045c35606c0166f989203e9593994 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Thu, 6 Mar 2025 14:48:25 +0100 Subject: [PATCH 09/12] use MustNewDecFromStr bis --- x/gov/abci.go | 14 ++------ x/gov/genesis.go | 6 +--- x/gov/keeper/grpc_query.go | 7 +--- x/gov/keeper/tally.go | 66 ++++++++++++-------------------------- x/gov/keeper/tally_test.go | 6 ++-- 5 files changed, 26 insertions(+), 73 deletions(-) diff --git a/x/gov/abci.go b/x/gov/abci.go index d3a69757..6cda55a0 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -69,12 +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 { - logger.Error("failed to check proposal quorum", "proposal", proposal.Id, - "title", proposal.Title, "err", err) - 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 @@ -141,12 +136,7 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) { keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal v1.Proposal) bool { var tagValue, logMsg string - passes, burnDeposits, tallyResults, err := keeper.Tally(ctx, proposal) - if err != nil { - logger.Error("failed to tally proposal", "proposal", proposal.Id, - "title", proposal.Title, "err", err) - return false - } + passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal) if burnDeposits { keeper.DeleteAndBurnDeposits(ctx, proposal.Id) diff --git a/x/gov/genesis.go b/x/gov/genesis.go index d14a8378..5eca7f01 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -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 diff --git a/x/gov/keeper/grpc_query.go b/x/gov/keeper/grpc_query.go index 006d662d..9335d4d8 100644 --- a/x/gov/keeper/grpc_query.go +++ b/x/gov/keeper/grpc_query.go @@ -285,12 +285,7 @@ func (q Keeper) TallyResult(c context.Context, req *v1.QueryTallyResultRequest) default: // proposal is in voting period - var err error - _, _, tallyResult, err = q.Tally(ctx, proposal) - if err != nil { - return nil, err - } - + _, _, tallyResult = q.Tally(ctx, proposal) } return &v1.QueryTallyResultResponse{Tally: &tallyResult}, nil diff --git a/x/gov/keeper/tally.go b/x/gov/keeper/tally.go index ba24ee49..0a896a8d 100644 --- a/x/gov/keeper/tally.go +++ b/x/gov/keeper/tally.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -13,7 +11,7 @@ import ( // Tally iterates over the votes and updates the tally of a proposal based on the voting power of the // voters -func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult, err error) { +func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult) { // fetch all the bonded validators currValidators := keeper.getBondedValidatorsByAddress(ctx) totalVotingPower, results := keeper.tallyVotes(ctx, proposal, currValidators, true) @@ -24,17 +22,14 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, // If there is no staked coins, the proposal fails totalBonded := keeper.sk.TotalBondedTokens(ctx) if totalBonded.IsZero() { - return false, false, tallyResults, nil + return false, false, tallyResults } // If there is not enough quorum of votes, the proposal fails percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded)) - quorum, threshold, err := keeper.getQuorumAndThreshold(ctx, proposal) - if err != nil { - return false, false, tallyResults, err - } + quorum, threshold := keeper.getQuorumAndThreshold(ctx, proposal) if percentVoting.LT(quorum) { - return false, params.BurnVoteQuorum, tallyResults, nil + return false, params.BurnVoteQuorum, tallyResults } // Compute non-abstaining voting power, aka active voting power @@ -42,13 +37,13 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, // If no one votes (everyone abstains), proposal fails if activeVotingPower.IsZero() { - return false, false, tallyResults, nil + return false, false, tallyResults } // If more than `threshold` of non-abstaining voters vote Yes, proposal passes. yesPercent := results[v1.OptionYes].Quo(activeVotingPower) if yesPercent.GT(threshold) { - return true, false, tallyResults, nil + return true, false, tallyResults } // If more than `burnDepositNoThreshold` of non-abstaining voters vote No, @@ -56,21 +51,21 @@ func (keeper Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDepositNoThreshold := sdk.MustNewDecFromStr(params.BurnDepositNoThreshold) noPercent := results[v1.OptionNo].Quo(activeVotingPower) if noPercent.GT(burnDepositNoThreshold) { - return false, true, tallyResults, nil + return false, true, tallyResults } // If less than `burnDepositNoThreshold` of non-abstaining voters vote No, // proposal is rejected but deposit is not burned. - return false, false, tallyResults, nil + return false, false, tallyResults } // HasReachedQuorum returns whether or not a proposal has reached quorum // this is just a stripped down version of the Tally function above -func (keeper Keeper) HasReachedQuorum(ctx sdk.Context, proposal v1.Proposal) (quorumPassed bool, err error) { +func (keeper Keeper) HasReachedQuorum(ctx sdk.Context, proposal v1.Proposal) bool { // If there is no staked coins, the proposal has not reached quorum totalBonded := keeper.sk.TotalBondedTokens(ctx) if totalBonded.IsZero() { - return false, nil + return false } /* DISABLED on AtomOne - no possible increase of computation speed by @@ -100,11 +95,8 @@ func (keeper Keeper) HasReachedQuorum(ctx sdk.Context, proposal v1.Proposal) (qu // check and return whether or not the proposal has reached quorum percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded)) - quorum, _, err := keeper.getQuorumAndThreshold(ctx, proposal) - if err != nil { - return false, err - } - return percentVoting.GTE(quorum), nil + quorum, _ := keeper.getQuorumAndThreshold(ctx, proposal) + return percentVoting.GTE(quorum) } // getBondedValidatorsByAddress fetches all the bonded validators and return @@ -187,16 +179,10 @@ func (keeper Keeper) tallyVotes( // getQuorumAndThreshold iterates over the proposal's messages to returns the // appropriate quorum and threshold. -func (keeper Keeper) getQuorumAndThreshold(ctx sdk.Context, proposal v1.Proposal) (sdk.Dec, sdk.Dec, error) { +func (keeper Keeper) getQuorumAndThreshold(ctx sdk.Context, proposal v1.Proposal) (sdk.Dec, sdk.Dec) { params := keeper.GetParams(ctx) - quorum, err := sdk.NewDecFromStr(params.Quorum) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.Quorum: %w", err) - } - threshold, err := sdk.NewDecFromStr(params.Threshold) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.Threshold: %w", err) - } + quorum := sdk.MustNewDecFromStr(params.Quorum) + threshold := sdk.MustNewDecFromStr(params.Threshold) // Check if a proposal message is an ExecLegacyContent message if len(proposal.Messages) > 0 { @@ -207,32 +193,20 @@ func (keeper Keeper) getQuorumAndThreshold(ctx sdk.Context, proposal v1.Proposal // quorum and threshold accordingly switch sdkMsg.(type) { case *v1.MsgProposeConstitutionAmendment: - q, err := sdk.NewDecFromStr(params.ConstitutionAmendmentQuorum) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.ConstitutionAmendmentQuorum: %w", err) - } + q := sdk.MustNewDecFromStr(params.ConstitutionAmendmentQuorum) if quorum.LT(q) { quorum = q } - t, err := sdk.NewDecFromStr(params.ConstitutionAmendmentThreshold) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.ConstitutionAmendmentThreshold: %w", err) - } + t := sdk.MustNewDecFromStr(params.ConstitutionAmendmentThreshold) if threshold.LT(t) { threshold = t } case *v1.MsgProposeLaw: - q, err := sdk.NewDecFromStr(params.LawQuorum) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.LawQuorum: %w", err) - } + q := sdk.MustNewDecFromStr(params.LawQuorum) if quorum.LT(q) { quorum = q } - t, err := sdk.NewDecFromStr(params.LawThreshold) - if err != nil { - return sdk.Dec{}, sdk.Dec{}, fmt.Errorf("parsing params.LawThreshold: %w", err) - } + t := sdk.MustNewDecFromStr(params.LawThreshold) if threshold.LT(t) { threshold = t } @@ -240,5 +214,5 @@ func (keeper Keeper) getQuorumAndThreshold(ctx sdk.Context, proposal v1.Proposal } } } - return quorum, threshold, nil + return quorum, threshold } diff --git a/x/gov/keeper/tally_test.go b/x/gov/keeper/tally_test.go index f1edaefe..db9f3672 100644 --- a/x/gov/keeper/tally_test.go +++ b/x/gov/keeper/tally_test.go @@ -490,9 +490,8 @@ func TestTally(t *testing.T) { tt.setup(s) } - pass, burn, tally, err := govKeeper.Tally(ctx, proposal) + pass, burn, tally := govKeeper.Tally(ctx, proposal) - require.NoError(t, err) assert.Equal(t, tt.expectedPass, pass, "wrong pass") assert.Equal(t, tt.expectedBurn, burn, "wrong burn") assert.Equal(t, tt.expectedTally, tally) @@ -603,9 +602,8 @@ func TestHasReachedQuorum(t *testing.T) { suite := newTallyFixture(t, ctx, proposal, valAddrs, delAddrs, govKeeper, mocks) tt.setup(suite) - quorum, err := govKeeper.HasReachedQuorum(ctx, proposal) + quorum := govKeeper.HasReachedQuorum(ctx, proposal) - require.NoError(t, err) assert.Equal(t, tt.expectedQuorum, quorum) if tt.expectedQuorum { // Assert votes are still here after HasReachedQuorum From 83458d31b9abd14c3ba40937c6c02b9a7e3a54b0 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Mon, 10 Mar 2025 21:13:11 +0100 Subject: [PATCH 10/12] Update x/gov/README.md Co-authored-by: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> --- x/gov/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/gov/README.md b/x/gov/README.md index 9aeac91a..2492b50f 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -204,7 +204,7 @@ 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 burnt. +its deposit is burned. #### Weighted Votes From 3788dbb753f4837ff4287a9be2c47e0d10c14906 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Tue, 11 Mar 2025 10:02:56 +0100 Subject: [PATCH 11/12] fix: validate burnDepositNoThreshold among other thresholds --- x/gov/keeper/msg_server_test.go | 41 ++++++++++++++++++++++++++++++--- x/gov/types/v1/params.go | 10 ++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/x/gov/keeper/msg_server_test.go b/x/gov/keeper/msg_server_test.go index 049c8a9d..7d70d11b 100644 --- a/x/gov/keeper/msg_server_test.go +++ b/x/gov/keeper/msg_server_test.go @@ -1262,10 +1262,12 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { expErrMsg: "invalid burnDepositNoThreshold string", }, { - name: "negative burnDepositNoThreshold", + name: "burnDepositNoThreshold <= 1 - constitutionThreshold", input: func() *v1.MsgUpdateParams { params1 := params - params1.BurnDepositNoThreshold = "-0.1" + params1.LawThreshold = "0.8" + params1.ConstitutionAmendmentThreshold = "0.8" + params1.BurnDepositNoThreshold = "0.2" return &v1.MsgUpdateParams{ Authority: authority, @@ -1273,7 +1275,40 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { } }, expErr: true, - expErrMsg: "burnDepositNoThreshold must be positive", + expErrMsg: "burnDepositNoThreshold must greater than 1-constitutionThreshold", + }, + { + name: "burnDepositNoThreshold <= 1 - lawThreshold", + input: func() *v1.MsgUpdateParams { + params1 := params + params1.ConstitutionAmendmentThreshold = "0.9" + params1.LawThreshold = "0.8" + params1.BurnDepositNoThreshold = "0.2" + + return &v1.MsgUpdateParams{ + Authority: authority, + Params: params1, + } + }, + expErr: true, + expErrMsg: "burnDepositNoThreshold must greater 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.4" + + return &v1.MsgUpdateParams{ + Authority: authority, + Params: params1, + } + }, + expErr: true, + expErrMsg: "burnDepositNoThreshold must greater than 1-threshold", }, { name: "burnDepositNoThreshold > 1", diff --git a/x/gov/types/v1/params.go b/x/gov/types/v1/params.go index f5832063..e64fa8d9 100644 --- a/x/gov/types/v1/params.go +++ b/x/gov/types/v1/params.go @@ -427,8 +427,14 @@ func (p Params) ValidateBasic() error { if err != nil { return fmt.Errorf("invalid burnDepositNoThreshold string: %w", err) } - if !burnDepositNoThreshold.IsPositive() { - return fmt.Errorf("burnDepositNoThreshold must be positive: %s", burnDepositNoThreshold) + if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(amendmentThreshold)) { + return fmt.Errorf("burnDepositNoThreshold must greater than 1-constitutionThreshold") + } + if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(lawThreshold)) { + return fmt.Errorf("burnDepositNoThreshold must greater than 1-lawThreshold") + } + if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(threshold)) { + return fmt.Errorf("burnDepositNoThreshold must greater than 1-threshold") } if burnDepositNoThreshold.GT(math.LegacyOneDec()) { return fmt.Errorf("burnDepositNoThreshold too large: %s", burnDepositNoThreshold) From 876f6a4d4c4574ce7ad6463fb835c0f18c2a1728 Mon Sep 17 00:00:00 2001 From: Giuseppe Natale <12249307+giunatale@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:47:32 +0100 Subject: [PATCH 12/12] LTE -> LT --- x/gov/keeper/msg_server_test.go | 14 +++++++------- x/gov/types/v1/params.go | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/x/gov/keeper/msg_server_test.go b/x/gov/keeper/msg_server_test.go index 7d70d11b..64f1c46c 100644 --- a/x/gov/keeper/msg_server_test.go +++ b/x/gov/keeper/msg_server_test.go @@ -1262,12 +1262,12 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { expErrMsg: "invalid burnDepositNoThreshold string", }, { - name: "burnDepositNoThreshold <= 1 - constitutionThreshold", + name: "burnDepositNoThreshold <= 1 - amendmentThreshold", input: func() *v1.MsgUpdateParams { params1 := params params1.LawThreshold = "0.8" params1.ConstitutionAmendmentThreshold = "0.8" - params1.BurnDepositNoThreshold = "0.2" + params1.BurnDepositNoThreshold = "0.199" return &v1.MsgUpdateParams{ Authority: authority, @@ -1275,7 +1275,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { } }, expErr: true, - expErrMsg: "burnDepositNoThreshold must greater than 1-constitutionThreshold", + expErrMsg: "burnDepositNoThreshold cannot be lower than 1-amendmentThreshold", }, { name: "burnDepositNoThreshold <= 1 - lawThreshold", @@ -1283,7 +1283,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { params1 := params params1.ConstitutionAmendmentThreshold = "0.9" params1.LawThreshold = "0.8" - params1.BurnDepositNoThreshold = "0.2" + params1.BurnDepositNoThreshold = "0.199" return &v1.MsgUpdateParams{ Authority: authority, @@ -1291,7 +1291,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { } }, expErr: true, - expErrMsg: "burnDepositNoThreshold must greater than 1-lawThreshold", + expErrMsg: "burnDepositNoThreshold cannot be lower than 1-lawThreshold", }, { name: "burnDepositNoThreshold <= 1 - threshold", @@ -1300,7 +1300,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { params1.ConstitutionAmendmentThreshold = "0.8" params1.LawThreshold = "0.8" params1.Threshold = "0.6" - params1.BurnDepositNoThreshold = "0.4" + params1.BurnDepositNoThreshold = "0.399" return &v1.MsgUpdateParams{ Authority: authority, @@ -1308,7 +1308,7 @@ func (suite *KeeperTestSuite) TestMsgUpdateParams() { } }, expErr: true, - expErrMsg: "burnDepositNoThreshold must greater than 1-threshold", + expErrMsg: "burnDepositNoThreshold cannot be lower than 1-threshold", }, { name: "burnDepositNoThreshold > 1", diff --git a/x/gov/types/v1/params.go b/x/gov/types/v1/params.go index e64fa8d9..3cbe6980 100644 --- a/x/gov/types/v1/params.go +++ b/x/gov/types/v1/params.go @@ -427,14 +427,14 @@ func (p Params) ValidateBasic() error { if err != nil { return fmt.Errorf("invalid burnDepositNoThreshold string: %w", err) } - if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(amendmentThreshold)) { - return fmt.Errorf("burnDepositNoThreshold must greater than 1-constitutionThreshold") + if burnDepositNoThreshold.LT(math.LegacyOneDec().Sub(amendmentThreshold)) { + return fmt.Errorf("burnDepositNoThreshold cannot be lower than 1-amendmentThreshold") } - if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(lawThreshold)) { - return fmt.Errorf("burnDepositNoThreshold must greater than 1-lawThreshold") + if burnDepositNoThreshold.LT(math.LegacyOneDec().Sub(lawThreshold)) { + return fmt.Errorf("burnDepositNoThreshold cannot be lower than 1-lawThreshold") } - if burnDepositNoThreshold.LTE(math.LegacyOneDec().Sub(threshold)) { - return fmt.Errorf("burnDepositNoThreshold must greater than 1-threshold") + if burnDepositNoThreshold.LT(math.LegacyOneDec().Sub(threshold)) { + return fmt.Errorf("burnDepositNoThreshold cannot be lower than 1-threshold") } if burnDepositNoThreshold.GT(math.LegacyOneDec()) { return fmt.Errorf("burnDepositNoThreshold too large: %s", burnDepositNoThreshold)