Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# ADR 003: Governance Proposal Deposit Auto-Throttler

**Changelog**
## **Changelog**

- 26 Nov 2024: Initial draft
- 3 Dec 2024: Improve formulation
- 16 Dec 2024: Write remaining sections
- 17 Dec 2024: Finalize first revision
- 26 Mar 2025: Revise model

## **Status[](https://docs.cosmos.network/main/architecture/adr-template#status)**

Expand All @@ -15,7 +16,7 @@ Implemented (https://github.com/atomone-hub/atomone/pull/69)

This ADR proposes a mechanism to dynamically adjust the value of `MinDeposit` in the `x/gov` module, and potentially the value `MinInitialDeposit` with a similar but independent mechanism. These parameters represent the minimum deposit required to submit a proposal (`MinInitialDeposit`) and to start its voting period (`MinDeposit`) - or *activate* a proposal.

The `MinDeposit` value will dynamically adjust with time and upon proposals *activation* or *deactivation*. The goal is to prevent an excessive number of active proposals and give individuals enough time to study them and vote with complete information.
The `MinDeposit` value will dynamically adjust with time (for decreases) and upon proposals *activation* (for increases). The goal is to prevent an excessive number of active proposals and give individuals enough time to study them and vote with more complete information.

While the ADR is focused on presenting the dynamic adjustment of `MinDeposit`, the same mechanism can be used to dynamically adjust `MinInitialDeposit` - with different values for parameters and focusing on proposals entering or exiting the deposit period instead of the voting period. The two dynamic systems would function in the same way, but independently of each other, allowing even `MinInitialDeposit` to grow bigger than `MinDeposit` at times.

Expand Down Expand Up @@ -60,55 +61,55 @@ While the following description focuses on `MinDeposit`, the system can be repli

### Dynamically adjusting the `MinDeposit`

The `MinDeposit` should exponentially increase and decrease with time and upon proposal activation and deactivation to target having on average *N* proposals active at any time, with *N* being a low number, e.g. 1 or 2. The dynamic value will have a floor below which it will not be able to go. Conversely, it is allowed to grow with no bounds.
The `MinDeposit` should exponentially increase -- upon proposal activation -- and decrease -- with time -- to target having on average *N* proposals active at any time, with *N* being a low number, e.g. 1 or 2. The dynamic value will have a floor below which it will not be able to go. Conversely, it is allowed to grow with no bounds.

An update of the `MinDeposit` can be triggered **by the passage of a certain amount of time** - denoted as a *tick -* or **by the activation or deactivation of a proposal**, and the new value is calculated from the previous value as such:
An update of the `MinDeposit` can be triggered **by the passage of a certain amount of time for decreases** - denoted as a *tick* - or **by the activation of a proposal when doing an increase**, and the new value is calculated from the previous value as such:

$$
D_{t+1} = \max(D_{\min}, D_t \times (1+ sign(n_t - N) \times \alpha \times \sqrt[k]{| n_t - N |}))
D_{t+1} = \max(D_{\min}, D_t \times (1 + \alpha \times \sigma))
$$

$$
\alpha = \begin{cases} \alpha_{up} & n_t \gt N \\
\alpha_{down} & n_t \leq N
\alpha = \begin{cases} \alpha_{up} & n_t \geq N \\
-\alpha_{down} & n_t \lt N
\end{cases}
$$

$$
sign(n_t - N) = \begin{cases} 1 & n_t \geq N \\
-1 & n_t \lt N
\sigma = \begin{cases} 1 & n_t \geq N \\
\sqrt[k]{| n_t - N |} & n_t \lt N
\end{cases}
$$

$$k \in {1, 2, 3, ...}$$
$$0 \lt \alpha_{down} \lt 1$$
$$0 \lt \alpha_{up} \lt 1$$
$$\alpha_{down} \lt \alpha_{up}$$
$$ k \in {1, 2, 3, ...}$$
$$ 0 \lt \alpha_{down} \lt 1$$
$$ 0 \lt \alpha_{up} \lt 1$$
$$ \alpha_{down} \lt \alpha_{up}$$

Where:

- $D_{t+1}$ is the new deposit value, $D_{\min}$ is the floor deposit value and $D_t$ the deposit at time $t$
- $n_t$ is the number of active proposals at time $t$ and $N$ the target number of proposals
- $k$ is a positive integer that expresses the *sensitivity* of the rate of increase or decrease to the distance of the number of active proposals $n_t$ with respect to the target $N$. Since the rate of change is proportional to this distance, $k$ can be used to tune how much getting further away from $N$ exacerbates things.
- $\alpha_{up}$ is the base rate of increase for each tick when the number of proposals exceeds the target by 1, and is a positive number between 0 and 1 (excluded)
- $\alpha_{down}$ is the base rate of decrease when the number of proposals is exactly at the target, , and is a positive number between 0 and 1 (excluded)
- $k$ is a positive integer that expresses the *sensitivity* of the rate of decrease to the distance of the number of active proposals $n_t$ with respect to the target $N$. Since the rate of decrease is proportional to this distance, $\k$ can be used to tune how much getting further away from $N$ affects the speed of decrease.
- $\alpha_{up}$ is the base rate of increase for each new proposal activation when the number of proposals equal or bigger than the target, and is a positive number between 0 and 1 (excluded)
- $\alpha_{down}$ is the base rate of decrease when the number of proposals is below target, and is a positive number between 0 and 1 (excluded)
- $\alpha_{down} \lt \alpha_{up}$ implies that the rate of decrease will be slower than the rate of increase. A typical value might be $\alpha_{down} = \frac{\alpha_{up}}{2}$

### Regarding update frequency of $D_t$ and the possibility to update it lazily

In a naive implementation for time-based updates every $T$ time has elapsed (a *tick*) the current value of $n_t$ should be used to calculate $D_t$. Having the accurate $D_t$ available to query at all times is important to be able to correctly submit a proposal.
But in reality, all we need to know is when $n_t$ **actually** changed the last time (which happens when proposals enter or leave the active proposals queue, and in itself also trigger a `MinDeposit` update), and how much time has passed since then, anytime the $D_t$ value is requested.
The straightforward implementation for time-based decreases is such that every $T$ time has elapsed (a *tick*) the current value of $n_t$ should be used to calculate $D_t$. Having the accurate $D_t$ available to query at all times is important to be able to correctly submit a proposal.
But in reality, all we need to know is when $n_t$ **actually** changed the last time (which happens when proposals enters the active proposals queue, and in itself also trigger a `MinDeposit` increase), and how much time has passed since then, anytime the $D_t$ value is requested.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this optimization has been discarded right ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to remove it from the ADR though. The ADR just states it's possible, and present the mathematical relation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I removed the section talking about it in the implementation.

Assume $n_{t_1}$ changed at a certain time $t_1$ (and $D_t$ was last updated at that time so it is equal to $D_{t_1}$), and has not changed up to a certain time $t_2$ when the deposit is queried or requested for a new proposal. At time $t_2$ all we need to know is $\tau = ticks = \lfloor \frac{\Delta t}{T} \rfloor = \lfloor \frac{t_2 - t_1}{T} \rfloor$ and then a way to compute easily $D_{t_2}$ as a function of $D_{t_1}$ and the number of ticks elapsed $\tau$. Due to the fact that $n_t$ has not changed since $t_1$ and is still $n_{t_1}$, all we need is to do is apply the same rate of decrease/increase multiple times, i.e.

$$
D_{t+1} = \max(D_{\min}, D_t \times (1+ sign(n_t - N) \times \alpha \times \sqrt[k]{| n_t - N |})^{\tau})
D_{t+1} = \max(D_{\min}, D_t \times (1 + \alpha \times \sigma)^{\tau})
$$

What does this mean in practice?

1. The deposit value need to only be actually updated in state when $n_t$ changes, i.e. when proposals actually enter or exit the voting period. Time-based updates can be avoided, and the current value calculated on-demand. To further simplify the computation, the value of $1+\alpha \times \sqrt[k]{| n_t - N |}$ could also be computed at this time (and maybe cached by nodes in memory instead of being stored in blockchain state) since it also won’t change until $n_t$ also changes again.
1. The deposit value need to only be actually updated in state when $n_t$ changes, i.e. when proposals actually enter or exit the voting period. Time-based updates can be avoided in principle, and the current value can be calculated on-demand. To further simplify the computation, the value of $1+\alpha \times \sigma$ could also be computed at this time (and maybe cached by nodes in memory instead of being stored in blockchain state) since it also won’t change until $n_t$ also changes again.
2. If the current deposit value at a certain time $t$ is requested - say $t_1$ was the last time the min deposit was actually updated in state due to a change of the total active proposals $n_t$, it can be computed lazily as
$D_t = \max( D_{\min}, D_{t_1} \times \gamma^\tau)$, where again $\tau = \lfloor \frac{t - t_1}{T} \rfloor$ - i.e. how many ticks have passed since $t_1$, and $\gamma = 1+ sign(n_t - N) \times \alpha \times \sqrt[k]{| n_t - N |}$
$D_t = \max( D_{\min}, D_{t_1} \times \gamma^\tau)$, where again $\tau = \lfloor \frac{t - t_1}{T} \rfloor$ - i.e. how many ticks have passed since $t_1$, and $\gamma = 1 + \alpha \times \sigma}$

### Implementation

Expand All @@ -117,25 +118,25 @@ The following parameters are added to the `x/gov` module params, inside a dedica
- `floor_value`: floor value for the minimum deposit required for a proposal to enter the voting period
- `update_period`: duration that dictates after how long the dynamic minimum deposit should be recalculated for time-based updates.
- `target_active_proposals`: the number of active proposals the dynamic minimum deposit should target.
- `increase_ratio`: the ratio of increase for the minimum deposit when the number of active proposals exceeds the target by 1.
- `decrease_ratio`: the ratio of increase for the minimum deposit when the number of active proposals is 1 less than the target.
- `sensitivity_target_distance`: A positive integer representing the sensitivity of the dynamic minimum deposit increase/decrease to the distance from the target number of active proposals. The higher the number, the lower the sensitivity. A value of 1 represents the highest sensitivity.
- `increase_ratio`: the ratio of increase for the minimum deposit when the number of active proposals is at or above the target.
- `decrease_ratio`: the ratio of increase for the minimum deposit when the number of active proposals is below the target.
- `decrease_sensitivity_target_distance`: A positive integer representing the sensitivity to the distance from the target number of active proposals for time-based decreases of the minimum deposit. The higher the number, the lower the sensitivity. A value of 1 represents the highest sensitivity.

Proposals activation or deactivation - i.e. when a proposal is added or removed from the `keeper.ActiveProposalsQueue` - triggers a `MinDeposit` update which is also saved in state via setting the `LastMinDeposit`. Both the computed value and the time at which this was done (the `ctx.BlockTime()`) are stored. Whenever the `MinDeposit` is queried or requested the value is then calculated lazily using the value and timestamp of `LastMinDeposit` and computing the *ticks* passed since `LastMinDeposit.Time`, using the formula detailed above.
Proposals activation - i.e. when a proposal is added to the `keeper.ActiveProposalsQueue` - triggers a `MinDeposit` increase which is also saved in state via setting the `LastMinDeposit` if number of active proposals is at or above `target_active_proposals`. In the `EndBlocker` time-based updates are performed to decrease the `MinDeposit` -- by `setting LastMinDeposit` -- value if the `update_period` has elapsed since the last update, and the number of active proposals is below the target.

### Querying deposits value

Because the `MinDeposit` is no longer a fixed parameter, a new query endpoint is also required. The new endpoint will allow clients to fetch the expected price of the deposit.

### Replicating the same system for `MinInitialDeposit`

The same system described for `MinDeposit` could also be replicated for `MinInitialDeposit`. The only difference with the above formulation is that $n_t$ in this case is the number of proposals in deposit period, and updates of `MinInitialDeposit` would be triggered upon proposals entering or exiting the deposit period (the `keeper.InactiveProposalsQueue` in `x/gov`) and with time. Parameters would be an exact replica - a copy of the decicated struct just starting with `min_initial_deposit_*` instead - and the implementation would equally be very similar.
The same system described for `MinDeposit` could also be replicated for `MinInitialDeposit`. The only difference with the above formulation is that $n_t$ in this case is the number of proposals in deposit period, and updates of `MinInitialDeposit` would be triggered upon proposals entering the deposit period (the `keeper.InactiveProposalsQueue` in `x/gov`) and with time. Parameters would be an exact replica - a copy of the decicated struct just starting with `min_initial_deposit_*` instead - and the implementation would equally be very similar.

The two systems should be independent of each other and have no relation. The value of `MinInitialDeposit` should be allowed to even grow higher than `MinDeposit` in response to sudden spikes in inactive proposals. The dynamic system for `MinInitialDeposit` would have to be fine-tuned differently with respect to the one for `MinDeposit` to respond more rapidly to changes but also be able to decade quicker.

## **Consequences**

Simply put, if the total number of active proposals exceeds the target, the `MinDeposit` will exponentially increase over time. Also, the the higher the number of active proposals past the target threshold, the faster the increase will be. The `MinDeposit` can also decrease if the number of active proposals goes below the target, with a faster decrease the lower the number of active proposals. However, the value cannot go lower than a set `MinDepositFloor`.
Simply put, if the total number of active proposals exceeds the target, the `MinDeposit` will start exponentially increasing as more proposals become active. The `MinDeposit` can also decrease with time if the number of active proposals goes below the target, with a faster decrease the lower the number of active proposals. However, the value cannot go lower than a set `MinDepositFloor`.

### **Backwards Compatibility[](https://docs.cosmos.network/main/architecture/adr-template#backwards-compatibility)**

Expand Down
20 changes: 10 additions & 10 deletions proto/atomone/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -225,25 +225,25 @@ message MinDepositThrottler {
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];

// Duration that dictates after how long the dynamic minimum deposit should be recalculated
// for time-based updates.
// for time-based decreases.
google.protobuf.Duration update_period = 2 [(gogoproto.stdduration) = true];

// The number of active proposals the dynamic minimum deposit should target.
uint64 target_active_proposals = 3;

// The ratio of increase for the minimum deposit when the number of active proposals
// exceeds the target by 1.
// is at or above the target.
string increase_ratio = 4 [(cosmos_proto.scalar) = "cosmos.Dec"];

// The ratio of decrease for the minimum deposit when the number of active proposals
// is 1 less than the target.
string decrease_ratio = 5 [(cosmos_proto.scalar) = "cosmos.Dec"];

// A positive integer representing the sensitivity of the dynamic minimum deposit
// increase/decrease to the distance from the target number of active proposals.
// A positive integer representing the sensitivity of dynamic minimum deposit
// decreases to the distance from the target number of active proposals.
// The higher the number, the lower the sensitivity. A value of 1 represents the
// highest sensitivity.
uint64 sensitivity_target_distance = 6;
uint64 decrease_sensitivity_target_distance = 6;
}

message MinInitialDepositThrottler {
Expand All @@ -252,25 +252,25 @@ message MinInitialDepositThrottler {
[ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ];

// Duration that dictates after how long the dynamic minimum deposit should be recalculated
// for time-based updates.
// for time-based decreases.
google.protobuf.Duration update_period = 2 [(gogoproto.stdduration) = true];

// The number of proposals in deposit period the dynamic minimum initial deposit should target.
uint64 target_proposals = 3;

// The ratio of increase for the minimum initial deposit when the number of proposals
// in deposit period exceeds the target by 1.
// in deposit period is at or above the target.
string increase_ratio = 4 [(cosmos_proto.scalar) = "cosmos.Dec"];

// The ratio of decrease for the minimum initial deposit when the number of proposals
// in deposit period is 1 less than the target.
string decrease_ratio = 5 [(cosmos_proto.scalar) = "cosmos.Dec"];

// A positive integer representing the sensitivity of the dynamic minimum initial
// deposit increase/decrease to the distance from the target number of proposals
// A positive integer representing the sensitivity of dynamic minimum initial
// deposit decreases to the distance from the target number of proposals
// in deposit period. The higher the number, the lower the sensitivity. A value
// of 1 represents the highest sensitivity.
uint64 sensitivity_target_distance = 6;
uint64 decrease_sensitivity_target_distance = 6;
}

// Params defines the parameters for the x/gov module.
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
false, false, govv1.DefaultMinDepositRatio.String(),
govv1.DefaultQuorumTimeout, govv1.DefaultMaxVotingPeriodExtension, govv1.DefaultQuorumCheckCount,
sdk.NewCoins(sdk.NewCoin(denom, depositAmount.Amount)), govv1.DefaultMinDepositUpdatePeriod,
govv1.DefaultMinDepositSensitivityTargetDistance,
govv1.DefaultMinDepositDecreaseSensitivityTargetDistance,
govv1.DefaultMinDepositIncreaseRatio.String(), govv1.DefaultMinDepositDecreaseRatio.String(),
govv1.DefaultTargetActiveProposals, sdk.NewCoins(sdk.NewCoin(denom, initialDepositAmount.Amount)), govv1.DefaultMinInitialDepositUpdatePeriod,
govv1.DefaultMinInitialDepositSensitivityTargetDistance, govv1.DefaultMinInitialDepositIncreaseRatio.String(),
govv1.DefaultMinInitialDepositDecreaseSensitivityTargetDistance, govv1.DefaultMinInitialDepositIncreaseRatio.String(),
govv1.DefaultMinInitialDepositDecreaseRatio.String(), govv1.DefaultTargetProposalsInDepositPeriod,
govv1.DefaultBurnDepositNoThreshold.String(),
),
Expand Down
Loading
Loading