Skip to content
Draft
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
35 changes: 10 additions & 25 deletions rs/nns/governance/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ use crate::{
HeapGovernanceData, XdrConversionRate, initialize_governance, reassemble_governance_proto,
split_governance_proto,
},
is_comprehensive_neuron_list_enabled, is_mission_70_voting_rewards_enabled,
is_neuron_follow_restrictions_enabled,
is_comprehensive_neuron_list_enabled, is_neuron_follow_restrictions_enabled,
neuron::{DissolveStateAndAge, Neuron, NeuronBuilder, Visibility},
neuron_data_validation::{NeuronDataValidationSummary, NeuronDataValidator},
neuron_store::{
Expand Down Expand Up @@ -305,15 +304,9 @@ pub const RELAXED_EIGHT_YEAR_GANG_MIN_DISSOLVE_DELAY_SECONDS: u64 =
// its ICP must not be newly staked. This defines "sufficiently old staked ICP".
pub const RELAXED_EIGHT_YEAR_GANG_MAX_AGING_SINCE_TIMESTAMP_SECONDS: u64 = 1_774_828_800;

/// Returns the maximum dissolve delay allowed for a neuron. After the flag is enabled, we can
/// replace `max_dissolve_delay_seconds()` with `MAX_DISSOLVE_DELAY_SECONDS` and set
/// `MAX_DISSOLVE_DELAY_SECONDS` to `MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70`.
/// Returns the maximum dissolve delay allowed for a neuron.
pub fn max_dissolve_delay_seconds() -> u64 {
if is_mission_70_voting_rewards_enabled() {
MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70
} else {
MAX_DISSOLVE_DELAY_SECONDS_PRE_MISSION_70
}
MAX_DISSOLVE_DELAY_SECONDS_POST_MISSION_70
}

impl GovernanceError {
Expand Down Expand Up @@ -1368,11 +1361,10 @@ impl Governance {
// Clamp all neuron dissolve delays to the Mission 70 maximum exactly once.
// The snapshot serves as the idempotency guard: if it's already populated,
// clamping has already run and we must not overwrite the pre-clamp record.
if is_mission_70_voting_rewards_enabled()
&& governance
.heap_data
.neuron_id_to_pre_clamp_dissolve_state
.is_empty()
if governance
.heap_data
.neuron_id_to_pre_clamp_dissolve_state
.is_empty()
{
let now = governance.env.now();
governance.heap_data.neuron_id_to_pre_clamp_dissolve_state = governance
Expand Down Expand Up @@ -4818,14 +4810,11 @@ impl Governance {
}

pub fn neuron_minimum_dissolve_delay_to_vote_seconds(&self) -> u64 {
let default = if is_mission_70_voting_rewards_enabled() {
VotingPowerEconomics::MISSION_70_DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS
} else {
VotingPowerEconomics::DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS
};
self.voting_power_economics()
.neuron_minimum_dissolve_delay_to_vote_seconds
.unwrap_or(default)
.unwrap_or(
VotingPowerEconomics::MISSION_70_DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS,
)
}

/// Reduces `neuron_minimum_dissolve_delay_to_vote_seconds` to 2 weeks if it is currently
Expand All @@ -4834,10 +4823,6 @@ impl Governance {
fn maybe_reduce_neuron_minimum_dissolve_delay_to_vote_seconds(
heap_data: &mut HeapGovernanceData,
) {
if !is_mission_70_voting_rewards_enabled() {
return;
}

let Some(voting_power_economics) = heap_data
.economics
.as_mut()
Expand Down
2 changes: 0 additions & 2 deletions rs/nns/governance/src/governance/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1896,7 +1896,6 @@ fn test_maybe_set_relaxed_eight_year_gang_bonus_base() {

#[test]
fn test_post_upgrade_migrates_neuron_minimum_dissolve_delay_to_vote_seconds() {
let _mission70 = crate::temporarily_enable_mission_70_voting_rewards();
// Simulate the old production state: 6 months.
let mut heap_data = HeapGovernanceData {
economics: Some(NetworkEconomics {
Expand Down Expand Up @@ -1930,7 +1929,6 @@ fn test_post_upgrade_migrates_neuron_minimum_dissolve_delay_to_vote_seconds() {
#[test]
#[should_panic(expected = "unexpectedly below 2 weeks")]
fn test_post_upgrade_panics_if_neuron_minimum_dissolve_delay_to_vote_seconds_below_two_weeks() {
let _mission70 = crate::temporarily_enable_mission_70_voting_rewards();
let one_week_seconds = 7 * ONE_DAY_SECONDS;

let mut heap_data = HeapGovernanceData {
Expand Down
23 changes: 0 additions & 23 deletions rs/nns/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,6 @@ thread_local! {

static ENABLE_SUBNET_SPLITTING_PROPOSALS: Cell<bool>
= const { Cell::new(false) };

// The planned effects of enabling this flag include
// 1. Reduce max dissolve delay from 8 years to 2 years. This includes capping existing neurons via data migration.
// 2. Reduce voting rewards pool by approximately 36.71% (equivalently, scale by 0.6329 times).
// 3. Dissolve delay bonus: quadratic instead of linear, with a maximum of 3x instead of 2x.
// 4. Reduce the minimum dissolve delay needed to vote to 2 weeks instead of 6 months.
// 5. 8 year gang 10% bonus.
static ENABLE_MISSION_70_VOTING_REWARDS: Cell<bool>
= const { Cell::new(true) };
}

thread_local! {
Expand Down Expand Up @@ -308,20 +299,6 @@ pub fn are_subnet_splitting_proposals_enabled() -> bool {
ENABLE_SUBNET_SPLITTING_PROPOSALS.get()
}

pub fn is_mission_70_voting_rewards_enabled() -> bool {
ENABLE_MISSION_70_VOTING_REWARDS.get()
}

#[cfg(any(test, feature = "canbench-rs", feature = "test"))]
pub fn temporarily_enable_mission_70_voting_rewards() -> Temporary {
Temporary::new(&ENABLE_MISSION_70_VOTING_REWARDS, true)
}

#[cfg(any(test, feature = "canbench-rs", feature = "test"))]
pub fn temporarily_disable_mission_70_voting_rewards() -> Temporary {
Temporary::new(&ENABLE_MISSION_70_VOTING_REWARDS, false)
}

pub fn decoder_config() -> DecoderConfig {
let mut config = DecoderConfig::new();
config.set_skipping_quota(DEFAULT_SKIPPING_QUOTA);
Expand Down
9 changes: 3 additions & 6 deletions rs/nns/governance/src/neuron/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::{
DEFAULT_VOTING_POWER_REFRESHED_TIMESTAMP_SECONDS,
governance::{LOG_PREFIX, MAX_NUM_HOT_KEYS_PER_NEURON, max_dissolve_delay_seconds},
is_mission_70_voting_rewards_enabled,
neuron::{
age_bonus_multiplier, combine_aged_stakes, dissolve_delay_bonus_multiplier,
dissolve_state_and_age::DissolveStateAndAge, neuron_stake_e8s,
Expand Down Expand Up @@ -380,11 +379,9 @@ impl Neuron {

// 8 Year Gang bonus. Cap the bonus base to the current stake because
// rejection fees can cause the bonus base to exceed stake_e8s.
if is_mission_70_voting_rewards_enabled() {
let eight_year_gang_bonus_base_e8s = self.eight_year_gang_bonus_base_e8s.min(stake_e8s);
potential_voting_power +=
Decimal::from(eight_year_gang_bonus_base_e8s) / Decimal::from(10) * boost;
}
let eight_year_gang_bonus_base_e8s = self.eight_year_gang_bonus_base_e8s.min(stake_e8s);
potential_voting_power +=
Decimal::from(eight_year_gang_bonus_base_e8s) / Decimal::from(10) * boost;

// For DECIDING voting power.
let adjustment_factor: Decimal = {
Expand Down
26 changes: 0 additions & 26 deletions rs/nns/governance/src/neuron/types/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::{
self as pb, VotingPowerEconomics,
manage_neuron::{Configure, SetDissolveTimestamp, StartDissolving, configure::Operation},
},
temporarily_disable_mission_70_voting_rewards, temporarily_enable_mission_70_voting_rewards,
};
use ic_cdk::println;
use ic_nervous_system_common::{E8, ONE_MONTH_SECONDS, ONE_YEAR_SECONDS};
Expand Down Expand Up @@ -935,8 +934,6 @@ fn test_eight_year_gang_bonus_base_e8s_is_lost_after_dissolving() {

#[test]
fn test_eight_year_gang_bonus_is_capped_to_stake_e8s() {
let _restore_on_drop = temporarily_enable_mission_70_voting_rewards();

let now = 123_456_789;

// 100 ICP stake, 100 ICP bonus base, 50 ICP in fees from rejected proposals.
Expand All @@ -958,26 +955,3 @@ fn test_eight_year_gang_bonus_is_capped_to_stake_e8s() {

assert_eq!(neuron.potential_voting_power(now), 55 * E8);
}

#[test]
fn test_eight_year_gang_bonus_not_applied_when_mission_70_disabled() {
let _restore_on_drop = temporarily_disable_mission_70_voting_rewards();

let now = 123_456_789;

// 100 ICP stake with a tagged 8y-gang bonus base, but mission 70 is disabled
// so the bonus must not be applied. With 0 dissolve delay and 0 age, both
// multipliers are 1.0, so potential_voting_power = 100 * 1 * 1 = 100 ICP.
let neuron = NeuronBuilder::new_for_test(
1,
DissolveStateAndAge::NotDissolving {
dissolve_delay_seconds: 0,
aging_since_timestamp_seconds: now,
},
)
.with_cached_neuron_stake_e8s(100 * E8)
.with_eight_year_gang_bonus_base_e8s(100 * E8)
.build();

assert_eq!(neuron.potential_voting_power(now), 100 * E8);
}
11 changes: 2 additions & 9 deletions rs/nns/governance/src/neuron/voting_power.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{
governance::{MAX_NEURON_AGE_FOR_AGE_BONUS, max_dissolve_delay_seconds},
is_mission_70_voting_rewards_enabled,
};
use crate::governance::{MAX_NEURON_AGE_FOR_AGE_BONUS, max_dissolve_delay_seconds};
use rust_decimal::Decimal;

/// Currently, only used by an integration test.
Expand All @@ -13,11 +10,7 @@ pub fn dissolve_delay_bonus_multiplier(dissolve_delay_seconds: u64) -> Decimal {
// t is (clamped) dissolve delay in units of max dissolve delay, so 0.0 <= t <= 1.0.
let t = Decimal::from(dissolve_delay_seconds) / Decimal::from(max_dissolve_delay_seconds);

(if is_mission_70_voting_rewards_enabled() {
Decimal::from(2) * t * t
} else {
t
}) + Decimal::from(1)
Decimal::from(2) * t * t + Decimal::from(1)
}

pub(crate) fn age_bonus_multiplier(age_seconds: u64) -> Decimal {
Expand Down
56 changes: 1 addition & 55 deletions rs/nns/governance/src/neuron/voting_power_tests.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,8 @@
use super::*;
use crate::{
temporarily_disable_mission_70_voting_rewards, temporarily_enable_mission_70_voting_rewards,
};
use ic_nervous_system_common::{ONE_MONTH_SECONDS, ONE_YEAR_SECONDS};

#[test]
fn test_dissolve_delay_bonus_multiplier_pre_mission_70() {
let _restore_on_drop = temporarily_disable_mission_70_voting_rewards();

// Test the endpoints.
assert_eq!(dissolve_delay_bonus_multiplier(0), Decimal::from(1));
assert_eq!(
dissolve_delay_bonus_multiplier(8 * ONE_YEAR_SECONDS),
Decimal::from(2)
);

// Test intermediate points.
assert_eq!(
dissolve_delay_bonus_multiplier(ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(1) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(2 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(2) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(3 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(3) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(4 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(4) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(5 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(5) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(6 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(6) / Decimal::from(8)
);
assert_eq!(
dissolve_delay_bonus_multiplier(7 * ONE_YEAR_SECONDS),
Decimal::from(1) + Decimal::from(7) / Decimal::from(8)
);

// Test beyond max dissolve delay. (No neuron should have this, but we can
// and do defend against it by clamping.)
assert_eq!(
dissolve_delay_bonus_multiplier(15 * ONE_YEAR_SECONDS),
Decimal::from(2)
);
}

#[test]
fn test_dissolve_delay_bonus_multiplier_while_mission_70_is_engaged() {
let _restore_on_drop = temporarily_enable_mission_70_voting_rewards();

fn test_dissolve_delay_bonus_multiplier() {
// Test the endpoints.
assert_eq!(dissolve_delay_bonus_multiplier(0), Decimal::from(1));
assert_eq!(
Expand Down
49 changes: 11 additions & 38 deletions rs/nns/governance/src/neuron_store/metrics/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::*;
use crate::{
governance::max_dissolve_delay_seconds,
is_mission_70_voting_rewards_enabled,
neuron::{DissolveStateAndAge, NeuronBuilder, dissolve_delay_bonus_multiplier},
pb::v1::{KnownNeuronData, MaturityDisbursement, NeuronType},
};
Expand Down Expand Up @@ -536,20 +535,10 @@ fn test_compute_neuron_metrics_non_self_authenticating() {

// Step 3: Inspect results.
//
// bucket_for_half_max: neuron_3 has a dissolve delay equal to max_dissolve_delay_seconds() / 2
// (mapped to bucket 8 when the max is long, 2 when the max is short).
let bucket_for_half_max = if is_mission_70_voting_rewards_enabled() {
2
} else {
8
};
// bucket_for_max: neuron_1 has a dissolve delay equal to max_dissolve_delay_seconds()
// (mapped to bucket 16 when the max is long, 4 when the max is short).
let bucket_for_max = if is_mission_70_voting_rewards_enabled() {
4
} else {
16
};
// bucket_for_half_max: neuron_3 has a dissolve delay equal to max_dissolve_delay_seconds() / 2.
let bucket_for_half_max = 2;
// bucket_for_max: neuron_1 has a dissolve delay equal to max_dissolve_delay_seconds().
let bucket_for_max = 4;

assert_eq!(
non_self_authenticating_controller_neuron_subset_metrics,
Expand Down Expand Up @@ -720,20 +709,10 @@ fn test_compute_neuron_metrics_public_neurons() {

// Step 3: Inspect results.
//
// bucket_for_half_max: neuron_3 has a dissolve delay equal to max_dissolve_delay_seconds() / 2
// (mapped to bucket 8 when the max is long, 2 when the max is short).
let bucket_for_half_max = if is_mission_70_voting_rewards_enabled() {
2
} else {
8
};
// bucket_for_max: neuron_1 has a dissolve delay equal to max_dissolve_delay_seconds()
// (mapped to bucket 16 when the max is long, 4 when the max is short).
let bucket_for_max = if is_mission_70_voting_rewards_enabled() {
4
} else {
16
};
// bucket_for_half_max: neuron_3 has a dissolve delay equal to max_dissolve_delay_seconds() / 2.
let bucket_for_half_max = 2;
// bucket_for_max: neuron_1 has a dissolve delay equal to max_dissolve_delay_seconds().
let bucket_for_max = 4;

assert_eq!(
public_neuron_subset_metrics,
Expand Down Expand Up @@ -895,15 +874,9 @@ fn test_compute_neuron_metrics_stale_and_expired_voting_power_neurons() {

// Step 3: Inspect results.
//
// All neurons are created with the maximum dissolve delay, and we bucket by
// that effective maximum:
// - Pre-mission-70: max dissolve delay is 8 years -> bucket 16
// - Post-mission-70: max dissolve delay is clamped to 2 years -> bucket 4
let bucket_for_max = if is_mission_70_voting_rewards_enabled() {
4
} else {
16
};
// All neurons are created with the maximum dissolve delay (2 years), which
// maps to bucket 4.
let bucket_for_max = 4;

assert_eq!(
declining_voting_power_neuron_subset_metrics,
Expand Down
10 changes: 3 additions & 7 deletions rs/nns/governance/src/neuron_store/voting_power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use ic_nns_governance_api::Vote;
use super::{NeuronStore, NeuronStoreError};

use crate::{
is_mission_70_voting_rewards_enabled,
neuron::Neuron,
pb::v1::{Ballot, NeuronIdToVotingPowerMap, VotingPowerEconomics, VotingPowerTotal},
storage::neurons::NeuronSections,
Expand Down Expand Up @@ -132,14 +131,11 @@ impl NeuronStore {
let mut total_deciding_voting_power: u128 = 0;
let mut total_potential_voting_power: u128 = 0;

let default_min_dissolve_delay = if is_mission_70_voting_rewards_enabled() {
VotingPowerEconomics::MISSION_70_DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS
} else {
VotingPowerEconomics::DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS
};
let min_dissolve_delay_seconds = voting_power_economics
.neuron_minimum_dissolve_delay_to_vote_seconds
.unwrap_or(default_min_dissolve_delay);
.unwrap_or(
VotingPowerEconomics::MISSION_70_DEFAULT_NEURON_MINIMUM_DISSOLVE_DELAY_TO_VOTE_SECONDS,
);

let mut process_neuron = |neuron: &Neuron| {
if neuron.is_inactive(now_seconds)
Expand Down
Loading
Loading