From e2e9a889007f6c643c16289f992e6b19e3eaace3 Mon Sep 17 00:00:00 2001 From: Jason Zhu Date: Tue, 14 Apr 2026 16:09:01 -0700 Subject: [PATCH 1/3] feat(governance): decouple minimum dissolve delay to vote from propose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The minimum dissolve delay to vote (`neuron_minimum_dissolve_delay_to_vote_seconds`) was also used to gate proposal submission. Under Mission 70, this was lowered to 2 weeks, meaning neurons with just 2 weeks of dissolve delay could submit proposals. This introduces a separate constant `NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS` (6 months) for proposal submission eligibility, while the vote minimum remains independently governable. A validation check is added to ensure the vote minimum cannot exceed the propose minimum, since every time a neuron submits a proposal it also votes on it — a proposer that is ineligible to vote on their own proposal would be incoherent. Co-Authored-By: Claude Opus 4.6 (1M context) --- rs/nns/governance/src/governance.rs | 18 ++++++++++++------ rs/nns/governance/src/network_economics.rs | 9 +++++++++ .../governance/src/network_economics_tests.rs | 16 ++++++++++++---- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/rs/nns/governance/src/governance.rs b/rs/nns/governance/src/governance.rs index 286d936b470e..797869ac35c3 100644 --- a/rs/nns/governance/src/governance.rs +++ b/rs/nns/governance/src/governance.rs @@ -285,6 +285,12 @@ pub const MAX_NEURONS_FUND_PARTICIPANTS: u64 = 5_000; /// in the same limit. const NEURON_RATE_LIMITER_KEY: &str = "ADD_NEURON"; +/// The minimum dissolve delay (in seconds) a neuron must have to submit a +/// non-ManageNeuron proposal. This is intentionally decoupled from the voting +/// eligibility threshold (`neuron_minimum_dissolve_delay_to_vote_seconds`), +/// which can be lower. +pub const NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS: u64 = 6 * ONE_MONTH_SECONDS; + // The maximum dissolve delay allowed for a neuron. pub const MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70: u64 = 8 * ONE_YEAR_SECONDS; pub const MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70: u64 = 2 * ONE_YEAR_SECONDS; @@ -5226,16 +5232,16 @@ impl Governance { )); } - let min_dissolve_delay_seconds_to_vote = if action.manage_neuron().is_some() { + let min_dissolve_delay_seconds_to_propose = if action.manage_neuron().is_some() { 0 } else { - self.neuron_minimum_dissolve_delay_to_vote_seconds() + NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS }; - // The proposer must be eligible to vote. This also ensures that the - // neuron cannot be dissolved until the proposal has been adopted or - // rejected. - if proposer_dissolve_delay_seconds < min_dissolve_delay_seconds_to_vote { + // The proposer must have sufficient dissolve delay to submit proposals. + // This threshold is intentionally decoupled from the voting eligibility + // threshold, which can be lower. + if proposer_dissolve_delay_seconds < min_dissolve_delay_seconds_to_propose { return Err(GovernanceError::new_with_message( ErrorType::InsufficientFunds, "Neuron's dissolve delay is too short.", diff --git a/rs/nns/governance/src/network_economics.rs b/rs/nns/governance/src/network_economics.rs index 2e5ab6255d64..b3188155bccd 100644 --- a/rs/nns/governance/src/network_economics.rs +++ b/rs/nns/governance/src/network_economics.rs @@ -1,3 +1,4 @@ +use crate::governance::NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS; use crate::pb::v1::{ NetworkEconomics, NeuronsFundEconomics, NeuronsFundMatchedFundingCurveCoefficients, VotingPowerEconomics, @@ -380,6 +381,14 @@ impl VotingPowerEconomics { ); defects.push(defect); } + if delay > NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS { + let defect = format!( + "neuron_minimum_dissolve_delay_to_vote_seconds ({}) must not exceed \ + NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS ({}).", + delay, NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS, + ); + defects.push(defect); + } } else { defects.push("neuron_minimum_dissolve_delay_to_vote_seconds must be set.".to_string()); } diff --git a/rs/nns/governance/src/network_economics_tests.rs b/rs/nns/governance/src/network_economics_tests.rs index 802d0e2f1678..84372dc0ae0a 100644 --- a/rs/nns/governance/src/network_economics_tests.rs +++ b/rs/nns/governance/src/network_economics_tests.rs @@ -112,10 +112,18 @@ fn test_neuron_minimum_dissolve_delay_to_vote_seconds_bounds() { ), ( Some(UPPER_BOUND_SECONDS + 1), - Err(vec![format!( - "neuron_minimum_dissolve_delay_to_vote_seconds (Some({})) must be between two weeks and six months.", - UPPER_BOUND_SECONDS + 1 - )]), + Err(vec![ + format!( + "neuron_minimum_dissolve_delay_to_vote_seconds (Some({})) must be between two weeks and six months.", + UPPER_BOUND_SECONDS + 1 + ), + format!( + "neuron_minimum_dissolve_delay_to_vote_seconds ({}) must not exceed \ + NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS ({}).", + UPPER_BOUND_SECONDS + 1, + crate::governance::NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS, + ), + ]), ), (Some(DEFAULT_SECONDS), Ok(())), (Some(LOWER_BOUND_SECONDS), Ok(())), From aac02fcdad481da9285a4dc1b8e7bbb6549077a2 Mon Sep 17 00:00:00 2001 From: Jason Zhu Date: Wed, 15 Apr 2026 22:12:16 -0700 Subject: [PATCH 2/3] address review feedback on dissolve delay decoupling - Reword validation defect to use domain terms instead of constant name - Use consistent {:?} formatting for the vote delay field - Add blank line before validation block - Simplify comment (remove redundant decoupling explanation) - Change ErrorType from InsufficientFunds to PreconditionFailed Co-Authored-By: Claude Opus 4.6 (1M context) --- rs/nns/governance/src/governance.rs | 4 +--- rs/nns/governance/src/network_economics.rs | 8 +++++--- rs/nns/governance/src/network_economics_tests.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rs/nns/governance/src/governance.rs b/rs/nns/governance/src/governance.rs index 797869ac35c3..2a258a054758 100644 --- a/rs/nns/governance/src/governance.rs +++ b/rs/nns/governance/src/governance.rs @@ -5239,11 +5239,9 @@ impl Governance { }; // The proposer must have sufficient dissolve delay to submit proposals. - // This threshold is intentionally decoupled from the voting eligibility - // threshold, which can be lower. if proposer_dissolve_delay_seconds < min_dissolve_delay_seconds_to_propose { return Err(GovernanceError::new_with_message( - ErrorType::InsufficientFunds, + ErrorType::PreconditionFailed, "Neuron's dissolve delay is too short.", )); } diff --git a/rs/nns/governance/src/network_economics.rs b/rs/nns/governance/src/network_economics.rs index b3188155bccd..4f42d65ced0d 100644 --- a/rs/nns/governance/src/network_economics.rs +++ b/rs/nns/governance/src/network_economics.rs @@ -381,11 +381,13 @@ impl VotingPowerEconomics { ); defects.push(defect); } + if delay > NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS { let defect = format!( - "neuron_minimum_dissolve_delay_to_vote_seconds ({}) must not exceed \ - NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS ({}).", - delay, NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS, + "neuron_minimum_dissolve_delay_to_vote_seconds ({:?}) must not exceed \ + the minimum dissolve delay required to submit proposals ({}).", + self.neuron_minimum_dissolve_delay_to_vote_seconds, + NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS, ); defects.push(defect); } diff --git a/rs/nns/governance/src/network_economics_tests.rs b/rs/nns/governance/src/network_economics_tests.rs index 84372dc0ae0a..0486269c2062 100644 --- a/rs/nns/governance/src/network_economics_tests.rs +++ b/rs/nns/governance/src/network_economics_tests.rs @@ -118,8 +118,8 @@ fn test_neuron_minimum_dissolve_delay_to_vote_seconds_bounds() { UPPER_BOUND_SECONDS + 1 ), format!( - "neuron_minimum_dissolve_delay_to_vote_seconds ({}) must not exceed \ - NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS ({}).", + "neuron_minimum_dissolve_delay_to_vote_seconds (Some({})) must not exceed \ + the minimum dissolve delay required to submit proposals ({}).", UPPER_BOUND_SECONDS + 1, crate::governance::NEURON_MINIMUM_DISSOLVE_DELAY_TO_PROPOSE_SECONDS, ), From 47b748b4acbdb015c6d77556aaee1fce4d7bcd1b Mon Sep 17 00:00:00 2001 From: Jason Zhu Date: Wed, 15 Apr 2026 22:16:13 -0700 Subject: [PATCH 3/3] update unreleased changelog for dissolve delay decoupling Co-Authored-By: Claude Opus 4.6 (1M context) --- rs/nns/governance/unreleased_changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rs/nns/governance/unreleased_changelog.md b/rs/nns/governance/unreleased_changelog.md index 50b21f2dccc3..ec471d76449c 100644 --- a/rs/nns/governance/unreleased_changelog.md +++ b/rs/nns/governance/unreleased_changelog.md @@ -13,6 +13,8 @@ on the process that this file is part of, see ## Changed +* The minimum dissolve delay required to submit non-manage-neuron proposals is now + a fixed 6 months, decoupled from the voting eligibility threshold which can be lower. * Mission 70 voting rewards adjustment has been re-calculated. Now: 63.29%. Before: 65.5%. ## Deprecated