Skip to content

Commit 78815ff

Browse files
authored
feat(x/gov): add dynamic min initial deposit (#72)
This PR replicates the dynamic system implemented for the `MinDeposit` also for `MinInitialDeposit`. For proposals submissions now, since the `MinInitialDeposit` can be lower or higher than the `MinDeposit` due to the two system behaving independently, the `MinDepositRatio` check is skipped (since the `MinInitialDeposit` threshold is the one to check against to see if submission make the proposal enter deposit period).
1 parent fdca78f commit 78815ff

29 files changed

+2069
-348
lines changed

proto/atomone/gov/v1/genesis.proto

+3
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@ message GenesisState {
3737

3838
// last updated value for the dynamic min deposit
3939
LastMinDeposit last_min_deposit = 10;
40+
41+
// last updated value for the dynamic min initial deposit
42+
LastMinDeposit last_min_initial_deposit = 11;
4043
}

proto/atomone/gov/v1/gov.proto

+30-1
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,33 @@ message MinDepositThrottler {
246246
uint64 sensitivity_target_distance = 6;
247247
}
248248

249+
message MinInitialDepositThrottler {
250+
// Floor value for the minimum initial deposit required for a proposal to enter the deposit period.
251+
repeated cosmos.base.v1beta1.Coin floor_value = 1
252+
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];
253+
254+
// Duration that dictates after how long the dynamic minimum deposit should be recalculated
255+
// for time-based updates.
256+
google.protobuf.Duration update_period = 2 [(gogoproto.stdduration) = true];
257+
258+
// The number of proposals in deposit period the dynamic minimum initial deposit should target.
259+
uint64 target_proposals = 3;
260+
261+
// The ratio of increase for the minimum initial deposit when the number of proposals
262+
// in deposit period exceeds the target by 1.
263+
string increase_ratio = 4 [(cosmos_proto.scalar) = "cosmos.Dec"];
264+
265+
// The ratio of decrease for the minimum initial deposit when the number of proposals
266+
// in deposit period is 1 less than the target.
267+
string decrease_ratio = 5 [(cosmos_proto.scalar) = "cosmos.Dec"];
268+
269+
// A positive integer representing the sensitivity of the dynamic minimum initial
270+
// deposit increase/decrease to the distance from the target number of proposals
271+
// in deposit period. The higher the number, the lower the sensitivity. A value
272+
// of 1 represents the highest sensitivity.
273+
uint64 sensitivity_target_distance = 6;
274+
}
275+
249276
// Params defines the parameters for the x/gov module.
250277
//
251278
// Since: cosmos-sdk 0.47
@@ -275,7 +302,7 @@ message Params {
275302
string threshold = 5 [(cosmos_proto.scalar) = "cosmos.Dec"];
276303

277304
// The ratio representing the proportion of the deposit value that must be paid at proposal submission.
278-
string min_initial_deposit_ratio = 7 [(cosmos_proto.scalar) = "cosmos.Dec"];
305+
string min_initial_deposit_ratio = 7 [(cosmos_proto.scalar) = "cosmos.Dec", deprecated = true ];
279306

280307
// burn deposits if a proposal does not meet quorum
281308
bool burn_vote_quorum = 13;
@@ -317,4 +344,6 @@ message Params {
317344
uint64 quorum_check_count = 22;
318345

319346
MinDepositThrottler min_deposit_throttler = 23;
347+
348+
MinInitialDepositThrottler min_initial_deposit_throttler = 24;
320349
}

proto/atomone/gov/v1/query.proto

+15
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ service Query {
6969
rpc MinDeposit(QueryMinDepositRequest) returns (QueryMinDepositResponse) {
7070
option (google.api.http).get = "/atomone/gov/v1/mindeposit";
7171
}
72+
73+
// MinInitialDeposit queries the minimum initial deposit
74+
// currently required for a proposal to be submitted.
75+
rpc MinInitialDeposit(QueryMinInitialDepositRequest) returns (QueryMinInitialDepositResponse) {
76+
option (google.api.http).get = "/atomone/gov/v1/mininitialdeposit";
77+
}
7278
}
7379

7480
// QueryConstitutionRequest is the request type for the Query/Constitution RPC method
@@ -226,3 +232,12 @@ message QueryMinDepositResponse {
226232
// min_deposit defines the minimum deposit required for a proposal to enter voting period.
227233
repeated cosmos.base.v1beta1.Coin min_deposit = 1 [ (gogoproto.nullable) = false];
228234
}
235+
236+
// QueryMinInitialDepositRequest is the request type for the Query/MinInitialDeposit RPC method.
237+
message QueryMinInitialDepositRequest {}
238+
239+
// QueryMinInitialDepositResponse is the response type for the Query/MinInitialDeposit RPC method.
240+
message QueryMinInitialDepositResponse {
241+
// min_initial_deposit defines the minimum initial deposit required for a proposal to be submitted.
242+
repeated cosmos.base.v1beta1.Coin min_initial_deposit = 1 [ (gogoproto.nullable) = false];
243+
}

tests/e2e/genesis.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
167167

168168
// Refactor to separate method
169169
amnt := sdk.NewInt(10000)
170+
initialDepositAmnt := sdk.NewInt(100)
170171
quorum, _ := sdk.NewDecFromStr("0.000000000000000001")
171172
threshold, _ := sdk.NewDecFromStr("0.000000000000000001")
172173
lawQuorum, _ := sdk.NewDecFromStr("0.000000000000000001")
@@ -184,13 +185,15 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
184185
votingPeriod,
185186
quorum.String(), threshold.String(),
186187
amendmentsQuorum.String(), amendmentsThreshold.String(), lawQuorum.String(), lawThreshold.String(),
187-
sdk.ZeroDec().String(),
188+
// sdk.ZeroDec().String(),
188189
false, false, govv1.DefaultMinDepositRatio.String(),
189190
govv1.DefaultQuorumTimeout, govv1.DefaultMaxVotingPeriodExtension, govv1.DefaultQuorumCheckCount,
190191
sdk.NewCoins(sdk.NewCoin(denom, amnt)), govv1.DefaultMinDepositUpdatePeriod,
191192
govv1.DefaultMinDepositSensitivityTargetDistance,
192193
govv1.DefaultMinDepositIncreaseRatio.String(), govv1.DefaultMinDepositDecreaseRatio.String(),
193-
govv1.DefaultTargetActiveProposals,
194+
govv1.DefaultTargetActiveProposals, sdk.NewCoins(sdk.NewCoin(denom, initialDepositAmnt)), govv1.DefaultMinInitialDepositUpdatePeriod,
195+
govv1.DefaultMinInitialDepositSensitivityTargetDistance, govv1.DefaultMinInitialDepositIncreaseRatio.String(),
196+
govv1.DefaultMinInitialDepositDecreaseRatio.String(), govv1.DefaultTargetProposalsInDepositPeriod,
194197
),
195198
)
196199
govGenState.Constitution = "This is a test constitution"

x/gov/abci.go

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) {
3939
keeper.DeleteAndBurnDeposits(ctx, proposal.Id) // burn the deposit if proposal got removed without getting 100% of the proposal
4040
}
4141

42+
keeper.DecrementInactiveProposalsNumber(ctx)
43+
4244
// called when proposal become inactive
4345
keeper.Hooks().AfterProposalFailedMinDeposit(ctx, proposal.Id)
4446

x/gov/abci_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,16 @@ func TestTickExpiredDepositPeriod(t *testing.T) {
7171
newHeader.Time = ctx.BlockHeader().Time.Add(*suite.GovKeeper.GetParams(ctx).MaxDepositPeriod)
7272
ctx = ctx.WithBlockHeader(newHeader)
7373

74+
require.EqualValues(t, 1, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
75+
7476
inactiveQueue = suite.GovKeeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
7577
require.True(t, inactiveQueue.Valid())
7678
inactiveQueue.Close()
7779

7880
gov.EndBlocker(ctx, suite.GovKeeper)
7981

82+
require.EqualValues(t, 0, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
83+
8084
inactiveQueue = suite.GovKeeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
8185
require.False(t, inactiveQueue.Valid())
8286
inactiveQueue.Close()
@@ -123,6 +127,8 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
123127
require.False(t, inactiveQueue.Valid())
124128
inactiveQueue.Close()
125129

130+
require.EqualValues(t, 1, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
131+
126132
newProposalMsg2, err := v1.NewMsgSubmitProposal(
127133
[]sdk.Msg{mkTestLegacyContent(t)},
128134
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 100000)},
@@ -145,6 +151,8 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
145151
require.True(t, inactiveQueue.Valid())
146152
inactiveQueue.Close()
147153

154+
require.EqualValues(t, 2, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
155+
148156
gov.EndBlocker(ctx, suite.GovKeeper)
149157

150158
inactiveQueue = suite.GovKeeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
@@ -159,8 +167,12 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
159167
require.True(t, inactiveQueue.Valid())
160168
inactiveQueue.Close()
161169

170+
require.EqualValues(t, 1, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
171+
162172
gov.EndBlocker(ctx, suite.GovKeeper)
163173

174+
require.EqualValues(t, 0, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
175+
164176
inactiveQueue = suite.GovKeeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
165177
require.False(t, inactiveQueue.Valid())
166178
inactiveQueue.Close()
@@ -212,6 +224,8 @@ func TestTickPassedDepositPeriod(t *testing.T) {
212224
require.False(t, inactiveQueue.Valid())
213225
inactiveQueue.Close()
214226

227+
require.EqualValues(t, 1, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
228+
215229
newDepositMsg := v1.NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 100000)})
216230

217231
res1, err := govMsgSvr.Deposit(sdk.WrapSDKContext(ctx), newDepositMsg)
@@ -221,6 +235,9 @@ func TestTickPassedDepositPeriod(t *testing.T) {
221235
activeQueue = suite.GovKeeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
222236
require.False(t, activeQueue.Valid())
223237
activeQueue.Close()
238+
239+
require.EqualValues(t, 1, suite.GovKeeper.GetInactiveProposalsNumber(ctx))
240+
require.EqualValues(t, 0, suite.GovKeeper.GetActiveProposalsNumber(ctx))
224241
}
225242

226243
func TestTickPassedVotingPeriod(t *testing.T) {

x/gov/client/cli/query.go

+33
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func GetQueryCmd() *cobra.Command {
4141
GetCmdQueryTally(),
4242
GetCmdConstitution(),
4343
GetCmdQueryMinDeposit(),
44+
GetCmdQueryMinInitialDeposit(),
4445
)
4546

4647
return govQueryCmd
@@ -709,3 +710,35 @@ $ %s query gov min-deposit
709710
},
710711
}
711712
}
713+
714+
// GetCmdQueryMinInitialDeposit implements the query min initial deposit command.
715+
func GetCmdQueryMinInitialDeposit() *cobra.Command {
716+
return &cobra.Command{
717+
Use: "min-initial-deposit",
718+
Args: cobra.ExactArgs(0),
719+
Short: "Query the minimum initial deposit needed for a proposal to enter deposit period",
720+
Long: strings.TrimSpace(
721+
fmt.Sprintf(`Query the minimum initial deposit needed for a proposal to enter deposit period.
722+
723+
Example:
724+
$ %s query gov min-initial-deposit
725+
`,
726+
version.AppName,
727+
),
728+
),
729+
RunE: func(cmd *cobra.Command, args []string) error {
730+
clientCtx, err := client.GetClientTxContext(cmd)
731+
if err != nil {
732+
return err
733+
}
734+
queryClient := v1.NewQueryClient(clientCtx)
735+
736+
resp, err := queryClient.MinInitialDeposit(cmd.Context(), &v1.QueryMinInitialDepositRequest{})
737+
if err != nil {
738+
return err
739+
}
740+
741+
return clientCtx.PrintProto(resp)
742+
},
743+
}
744+
}

x/gov/client/cli/query_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -406,3 +406,63 @@ func (s *CLITestSuite) TestCmdGetConstitution() {
406406
})
407407
}
408408
}
409+
410+
func (s *CLITestSuite) TestCmdQueryMinDeposit() {
411+
testCases := []struct {
412+
name string
413+
args []string
414+
expCmdOutput string
415+
}{
416+
{
417+
"query min deposit",
418+
[]string{},
419+
"",
420+
},
421+
{
422+
"query min deposit (json output)",
423+
[]string{
424+
fmt.Sprintf("--%s=json", flags.FlagOutput),
425+
},
426+
"--output=json",
427+
},
428+
}
429+
430+
for _, tc := range testCases {
431+
tc := tc
432+
s.Run(tc.name, func() {
433+
cmd := cli.GetCmdQueryMinDeposit()
434+
cmd.SetArgs(tc.args)
435+
s.Require().Contains(fmt.Sprint(cmd), strings.TrimSpace(tc.expCmdOutput))
436+
})
437+
}
438+
}
439+
440+
func (s *CLITestSuite) TestCmdQueryMinInitialDeposit() {
441+
testCases := []struct {
442+
name string
443+
args []string
444+
expCmdOutput string
445+
}{
446+
{
447+
"query min initial deposit",
448+
[]string{},
449+
"",
450+
},
451+
{
452+
"query min initial deposit (json output)",
453+
[]string{
454+
fmt.Sprintf("--%s=json", flags.FlagOutput),
455+
},
456+
"--output=json",
457+
},
458+
}
459+
460+
for _, tc := range testCases {
461+
tc := tc
462+
s.Run(tc.name, func() {
463+
cmd := cli.GetCmdQueryMinInitialDeposit()
464+
cmd.SetArgs(tc.args)
465+
s.Require().Contains(fmt.Sprint(cmd), strings.TrimSpace(tc.expCmdOutput))
466+
})
467+
}
468+
}

x/gov/genesis.go

+19-7
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ func InitGenesis(ctx sdk.Context, ak types.AccountKeeper, bk types.BankKeeper, k
8383
} else {
8484
k.SetLastMinDeposit(ctx, data.Params.MinDepositThrottler.FloorValue, ctx.BlockTime())
8585
}
86+
87+
if data.LastMinInitialDeposit != nil {
88+
k.SetLastMinInitialDeposit(ctx, data.LastMinInitialDeposit.Value, *data.LastMinInitialDeposit.Time)
89+
} else {
90+
k.SetLastMinInitialDeposit(ctx, data.Params.MinInitialDepositThrottler.FloorValue, ctx.BlockTime())
91+
}
8692
}
8793

8894
// ExportGenesis - output genesis parameters
@@ -108,13 +114,19 @@ func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) *v1.GenesisState {
108114
Time: &blockTime,
109115
}
110116

117+
lastMinInitialDeposit := v1.LastMinDeposit{
118+
Value: k.GetMinInitialDeposit(ctx),
119+
Time: &blockTime,
120+
}
121+
111122
return &v1.GenesisState{
112-
StartingProposalId: startingProposalID,
113-
Deposits: proposalsDeposits,
114-
Votes: proposalsVotes,
115-
Proposals: proposals,
116-
Params: &params,
117-
Constitution: constitution,
118-
LastMinDeposit: &lastMinDeposit,
123+
StartingProposalId: startingProposalID,
124+
Deposits: proposalsDeposits,
125+
Votes: proposalsVotes,
126+
Proposals: proposals,
127+
Params: &params,
128+
Constitution: constitution,
129+
LastMinDeposit: &lastMinDeposit,
130+
LastMinInitialDeposit: &lastMinInitialDeposit,
119131
}
120132
}

x/gov/genesis_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ func TestImportExportQueues_ErrorUnconsistentState(t *testing.T) {
3131
Value: sdk.NewCoins(expectedGenState.Params.MinDepositThrottler.FloorValue...),
3232
Time: &time.Time{},
3333
}
34+
expectedGenState.LastMinInitialDeposit = &v1.LastMinDeposit{
35+
Value: expectedGenState.Params.MinInitialDepositThrottler.FloorValue,
36+
Time: &time.Time{},
37+
}
3438
require.Panics(t, func() {
3539
gov.InitGenesis(ctx, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper, &v1.GenesisState{
3640
Deposits: v1.Deposits{
@@ -59,6 +63,9 @@ func TestInitGenesis(t *testing.T) {
5963
MinDepositThrottler: &v1.MinDepositThrottler{
6064
FloorValue: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(42))),
6165
},
66+
MinInitialDepositThrottler: &v1.MinInitialDepositThrottler{
67+
FloorValue: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(42))),
68+
},
6269
}
6370
quorumTimeout = time.Hour * 20
6471
paramsWithQuorumCheckEnabled = &v1.Params{
@@ -67,6 +74,9 @@ func TestInitGenesis(t *testing.T) {
6774
MinDepositThrottler: &v1.MinDepositThrottler{
6875
FloorValue: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(42))),
6976
},
77+
MinInitialDepositThrottler: &v1.MinInitialDepositThrottler{
78+
FloorValue: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(42))),
79+
},
7080
}
7181

7282
depositAmount = sdk.Coins{
@@ -175,6 +185,9 @@ func TestInitGenesis(t *testing.T) {
175185
lmdCoins, lmdTime := s.GovKeeper.GetLastMinDeposit(ctx)
176186
assert.EqualValues(t, p.MinDepositThrottler.FloorValue, lmdCoins)
177187
assert.Equal(t, ctx.BlockTime(), lmdTime)
188+
lmidCoins, lmidTime := s.GovKeeper.GetLastMinInitialDeposit(ctx)
189+
assert.EqualValues(t, p.MinInitialDepositThrottler.FloorValue, lmidCoins)
190+
assert.Equal(t, ctx.BlockTime(), lmidTime)
178191
},
179192
},
180193
{
@@ -193,6 +206,22 @@ func TestInitGenesis(t *testing.T) {
193206
assert.Equal(t, utcTime, lmdTime)
194207
},
195208
},
209+
{
210+
name: "ok: genesis with last min initial deposit",
211+
genesis: v1.GenesisState{
212+
Params: params,
213+
LastMinInitialDeposit: &v1.LastMinDeposit{
214+
Value: sdk.NewCoins(sdk.NewInt64Coin("xxx", 1)),
215+
Time: &utcTime,
216+
},
217+
},
218+
assert: func(t *testing.T, ctx sdk.Context, s suite) {
219+
t.Helper()
220+
lmidCoins, lmidTime := s.GovKeeper.GetLastMinInitialDeposit(ctx)
221+
assert.EqualValues(t, sdk.NewCoins(sdk.NewInt64Coin("xxx", 1)), lmidCoins)
222+
assert.Equal(t, utcTime, lmidTime)
223+
},
224+
},
196225
{
197226
name: "fail: genesis with deposits but module balance is not equal to total deposits",
198227
moduleBalance: depositAmount,

0 commit comments

Comments
 (0)