From c850333b63aeee7459c3aafa6c7f8f3e1b8ac24a Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Wed, 1 Apr 2026 14:19:45 +0200 Subject: [PATCH 1/4] Use Mission 70 voting rewards purse adjustment. --- rs/nns/governance/src/reward/calculation.rs | 204 +++++++++++++----- rs/nns/governance/src/voting.rs | 2 +- rs/nns/governance/tests/governance.rs | 166 ++++++++------ rs/nns/governance/tests/proposals.rs | 9 +- .../src/governance_neurons.rs | 8 +- 5 files changed, 266 insertions(+), 123 deletions(-) diff --git a/rs/nns/governance/src/reward/calculation.rs b/rs/nns/governance/src/reward/calculation.rs index b14beb01acf6..d94e1b39c4b6 100644 --- a/rs/nns/governance/src/reward/calculation.rs +++ b/rs/nns/governance/src/reward/calculation.rs @@ -14,7 +14,7 @@ //! * Floating point makes code easier since the reward pool is specified as a //! fraction of the total ICP supply. -use crate::pb::v1::RewardEvent; +use crate::{is_mission_70_voting_rewards_enabled, pb::v1::RewardEvent}; use std::ops::{Add, Div, Mul, Sub}; // ---- NON-BOILERPLATE CODE STARTS HERE ---------------------------------- @@ -56,21 +56,24 @@ pub const ONE_DAY: Duration = Duration { days: 1.0 }; /// distribution does not directly increase ICP supply. (It does indirectly, /// when neuron owners spawn neurons). Therefore there is no automatic /// compounding. -pub const INITIAL_VOTING_REWARD_RELATIVE_RATE: InverseDuration = InverseDuration { +const INITIAL_VOTING_REWARD_RELATIVE_RATE: InverseDuration = InverseDuration { per_day: 0.1 / AVERAGE_DAYS_PER_YEAR, // 10% per year }; /// The voting reward relative rate, at the end of times. /// /// See comment above for what "relative rate" precisely means. -pub const FINAL_VOTING_REWARD_RELATIVE_RATE: InverseDuration = InverseDuration { +const FINAL_VOTING_REWARD_RELATIVE_RATE: InverseDuration = InverseDuration { per_day: 0.05 / AVERAGE_DAYS_PER_YEAR, // 5% per year }; /// The date at which the reward rate reaches, and thereafter remains at, its /// final value. -pub const REWARD_FLATTENING_DATE: IcTimestamp = IcTimestamp { +const REWARD_FLATTENING_DATE: IcTimestamp = IcTimestamp { days_since_ic_genesis: 8.0 * AVERAGE_DAYS_PER_YEAR, }; +// See https://x.com/dominic_w/status/2011156143796781481 . +const MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR: f64 = 0.635; + /// Computes the reward to distribute, as a fraction of the ICP supply, for one /// day. pub fn rewards_pool_to_distribute_in_supply_fraction_for_one_day( @@ -94,7 +97,15 @@ pub fn rewards_pool_to_distribute_in_supply_fraction_for_one_day( let rate = FINAL_VOTING_REWARD_RELATIVE_RATE + variable_rate; - rate * ONE_DAY + voting_rewards_adjustment_factor() * rate * ONE_DAY +} + +fn voting_rewards_adjustment_factor() -> f64 { + if is_mission_70_voting_rewards_enabled() { + MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR + } else { + 1.0 + } } impl RewardEvent { @@ -231,6 +242,7 @@ impl Div for Duration { // assert_approx_eq! macro are unused. This is very strange, so // just tell clippy to keep quiet. #[allow(unused_imports, unused_macros)] +#[cfg(test)] mod test { use super::*; @@ -256,52 +268,146 @@ mod test { }}; } - #[test] - fn days_fully_after_flattening_produce_linear_reward() { - assert_approx_eq!( - rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366), - 0.05 / 365.25 - ); - assert_approx_eq!( - rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366 + 5), - 0.05 / 365.25 - ); - assert_approx_eq!( - rewards_pool_to_distribute_in_supply_fraction_for_one_day(123456), - 0.05 / 365.25 - ); - } + mod mission_70_disabled { + use super::*; + use crate::temporarily_disable_mission_70_voting_rewards; - #[test] - fn reward_for_first_day() { - assert_approx_eq!( - rewards_pool_to_distribute_in_supply_fraction_for_one_day(0), - 0.10 / 365.25 - ); - } + #[test] + fn days_fully_after_flattening_produce_linear_reward() { + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); - #[test] - fn reward_for_entire_pre_flattening_interval_can_be_lower_and_upper_bounded() { - let lower_bound = (REWARD_FLATTENING_DATE - GENESIS) * FINAL_VOTING_REWARD_RELATIVE_RATE; - let upper_bound = (REWARD_FLATTENING_DATE - GENESIS) * INITIAL_VOTING_REWARD_RELATIVE_RATE; - let actual = (0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64)) - .map(rewards_pool_to_distribute_in_supply_fraction_for_one_day) - .sum(); - assert!(lower_bound < actual); - assert!(actual < upper_bound); - } + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366), + 0.05 / 365.25 + ); + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366 + 5), + 0.05 / 365.25 + ); + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(123456), + 0.05 / 365.25 + ); + } - #[test] - fn reward_is_convex_and_decreasing() { - // Here we verify the convex inequality for all 3 consecutive days during the - // parabolic rate period. - for day in 0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64) - 2 { - let a = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day); - let b = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 1); - let c = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 2); - assert!(a > b); - assert!(b > c); - assert!(a + c > 2.0 * b); + #[test] + fn reward_for_first_day() { + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); + + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(0), + 0.10 / 365.25 + ); } - } + + #[test] + fn reward_for_entire_pre_flattening_interval_can_be_lower_and_upper_bounded() { + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); + + let lower_bound = + (REWARD_FLATTENING_DATE - GENESIS) * FINAL_VOTING_REWARD_RELATIVE_RATE; + let upper_bound = + (REWARD_FLATTENING_DATE - GENESIS) * INITIAL_VOTING_REWARD_RELATIVE_RATE; + let actual = (0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64)) + .map(rewards_pool_to_distribute_in_supply_fraction_for_one_day) + .sum(); + assert!(lower_bound < actual); + assert!(actual < upper_bound); + } + + #[test] + fn reward_is_convex_and_decreasing() { + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); + + // Here we verify the convex inequality for all 3 consecutive days during the + // parabolic rate period. + for day in 0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64) - 2 { + let a = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day); + let b = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 1); + let c = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 2); + + let d1 = b - a; + let d2 = c - b; + + // Decreasing. + assert!(d1 < 0.0, "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + assert!(d2 < 0.0, "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + + // Convex. That is, the second decrease is not as large as the first. + assert!(d1.abs() > d2.abs(), "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + } + } + } // mod mission_70_disabled + + mod mission_70_enabled { + use super::*; + use crate::temporarily_enable_mission_70_voting_rewards; + + #[test] + fn days_fully_after_flattening_produce_linear_reward() { + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366), + MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR * 0.05 / 365.25 + ); + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(8 * 366 + 5), + MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR * 0.05 / 365.25 + ); + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(123456), + MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR * 0.05 / 365.25 + ); + } + + #[test] + fn reward_for_first_day() { + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + + assert_approx_eq!( + rewards_pool_to_distribute_in_supply_fraction_for_one_day(0), + MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR * 0.10 / 365.25 + ); + } + + #[test] + fn reward_for_entire_pre_flattening_interval_can_be_lower_and_upper_bounded() { + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + + let lower_bound = MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR + * ((REWARD_FLATTENING_DATE - GENESIS) * FINAL_VOTING_REWARD_RELATIVE_RATE); + let upper_bound = MISSION_70_VOTING_REWARDS_ADJUSTMENT_FACTOR + * ((REWARD_FLATTENING_DATE - GENESIS) * INITIAL_VOTING_REWARD_RELATIVE_RATE); + let actual = (0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64)) + .map(rewards_pool_to_distribute_in_supply_fraction_for_one_day) + .sum(); + assert!(lower_bound < actual); + assert!(actual < upper_bound); + } + + /// This is a copy & paste of the test in the sibling mission_70_disabled module. + #[test] + fn reward_is_convex_and_decreasing() { + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + + // Here we verify the convex inequality for all 3 consecutive days during the + // parabolic rate period. + for day in 0..(REWARD_FLATTENING_DATE.days_since_ic_genesis as u64) - 2 { + let a = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day); + let b = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 1); + let c = rewards_pool_to_distribute_in_supply_fraction_for_one_day(day + 2); + + let d1 = b - a; + let d2 = c - b; + + // Decreasing. + assert!(d1 < 0.0, "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + assert!(d2 < 0.0, "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + + // Convex. That is, the second decrease is not as large as the first. + assert!(d1.abs() > d2.abs(), "{day}: {a}, {b}, {c} ({d1} vs. {d2}"); + } + } + } // mod mission_70_enabled } diff --git a/rs/nns/governance/src/voting.rs b/rs/nns/governance/src/voting.rs index 5e922aa33138..8b00489d928c 100644 --- a/rs/nns/governance/src/voting.rs +++ b/rs/nns/governance/src/voting.rs @@ -1530,7 +1530,7 @@ mod test { .neuron_store .with_neuron(&NeuronId { id: 1 }, |n| n.maturity_e8s_equivalent) .unwrap(), - 5474 + 3476, ); } diff --git a/rs/nns/governance/tests/governance.rs b/rs/nns/governance/tests/governance.rs index c63a3d82dfa7..bbfa899f4048 100644 --- a/rs/nns/governance/tests/governance.rs +++ b/rs/nns/governance/tests/governance.rs @@ -121,7 +121,6 @@ use icp_ledger::{ use lazy_static::lazy_static; use maplit::{btreemap, btreeset, hashmap}; use pretty_assertions::{assert_eq, assert_ne}; -use proptest::prelude::{ProptestConfig, proptest}; use rand::{Rng, SeedableRng, prelude::IteratorRandom, rngs::StdRng}; use registry_canister::mutations::do_add_node_operator::AddNodeOperatorPayload; use rust_decimal_macros::dec; @@ -2607,11 +2606,9 @@ async fn test_reward_event_proposals_last_longer_than_reward_period() { let genesis_timestamp_seconds = 56; let mut fake_driver = fake::FakeDriver::default() .at(genesis_timestamp_seconds) - // To make assertion easy to sanity-check, the total supply of ICPs is chosen - // so that the reward supply for the first day is 100 (365_250 * 10% / 365.25 = 100). - // On next days it will be a bit less, but it is still easy to verify by eye - // the order of magnitude. - .with_supply(Tokens::from_e8s(365_250)); + .with_supply(total_supply_for_target_voting_rewards_pot_at_genesis( + Tokens::from_e8s(100), + )); const INITIAL_REWARD_POT_PER_ROUND_E8S: u64 = 100; let mut fixture = fixture_two_neurons_second_is_bigger(); // Proposals last longer than the reward period @@ -2903,9 +2900,9 @@ async fn test_restricted_proposals_are_not_eligible_for_voting_rewards() { actual_timestamp_seconds: fake_driver.now(), settled_proposals: vec![], distributed_e8s_equivalent: 0, - total_available_e8s_equivalent: 338006, + total_available_e8s_equivalent: 214633, rounds_since_last_distribution: Some(1), - latest_round_available_e8s_equivalent: Some(338006) + latest_round_available_e8s_equivalent: Some(214633) } ); @@ -3180,9 +3177,9 @@ async fn test_reward_distribution_skips_deleted_neurons() { ); let mut fake_driver = fake::FakeDriver::default() .at(2500) // Just a little before the proposal happened. - // To make assertion easy to sanity-check, the total supply of ICPs is chosen - // so that the reward supply for the first day is 100 (365_250 * 10% / 365.25 = 100). - .with_supply(Tokens::from_e8s(365_250)); + .with_supply(total_supply_for_target_voting_rewards_pot_at_genesis( + Tokens::from_e8s(100), + )); fixture.wait_for_quiet_threshold_seconds = 5; // Let's set genesis let genesis_timestamp_seconds = fake_driver.now(); @@ -3258,11 +3255,9 @@ async fn test_reward_distribution_skips_deleted_neurons() { /// reward event after genesis, not the first. #[tokio::test] async fn test_genesis_in_the_future_is_supported() { - let mut fake_driver = fake::FakeDriver::default() - .at(78) - // To make assertion easy to sanity-check, the total supply of ICPs is chosen - // so that the reward supply for the first day is 100 (365_250 * 10% / 365.25 = 100). - .with_supply(Tokens::from_e8s(365_250)); + let mut fake_driver = fake::FakeDriver::default().at(78).with_supply( + total_supply_for_target_voting_rewards_pot_at_genesis(Tokens::from_e8s(100)), + ); let mut fixture = fixture_two_neurons_second_is_bigger(); fixture.wait_for_quiet_threshold_seconds = 2 * REWARD_DISTRIBUTION_PERIOD_SECONDS; fixture.short_voting_period_seconds = 13; @@ -3462,9 +3457,7 @@ async fn test_genesis_in_the_future_is_supported() { } /// Test helper where several proposals are created and voted on by -/// various neurons. 100 e8s of voting rewards are distributed and the -/// final maturities are returned, truncated to the nearest integer -/// (so they don't have to add up to 100). +/// various neurons. /// /// In this test, all proposals last 1 second, which is smaller than the reward /// period. This allows to have tests where everything interesting happens in @@ -3480,7 +3473,9 @@ fn compute_maturities( let mut fake_driver = fake::FakeDriver::default() .at(DEFAULT_TEST_START_TIMESTAMP_SECONDS) - .with_supply(Tokens::from_e8s(365_250 * reward_pot_e8s / 100)); + .with_supply(total_supply_for_target_voting_rewards_pot_at_genesis( + Tokens::from_e8s(reward_pot_e8s), + )); let neurons = stakes_e8s .iter() @@ -3587,50 +3582,75 @@ fn compute_maturities( .collect() } -proptest! { -#![proptest_config(ProptestConfig { - cases: 100, .. ProptestConfig::default() -})] #[test] -fn test_topic_weights(stake in 1_u64..1_000_000_000) { - // Check that voting on - // 1. a governance proposal yields 20 times the voting power - // 3. other proposals yield 1 time the voting power - - // Test alloacting 100 maturity to two neurons with equal stake where - // 1. first neuron voting on a gov proposal (20x) and - // 2. second neuron voting on a network proposal (1x). - // Overall reward weights are 2 * (20+1) = 42 - // First neuron gets 20/42 * 100 = 47.61 truncated to 47. - // Second neuron gets 1/42 * 100 = 2.38 truncated to 2. - assert_eq!( - compute_maturities(vec![stake, stake], vec!["P-G", "-PN"], USUAL_REWARD_POT_E8S), - vec![47, 2], - ); - - // First neuron proposes and votes on a governance proposal. - // Second neuron proposes and votes on five network economics proposals. - // The first neuron receives 20x the voting power and - // the second neuron receives 5x the voting power. - // Thus, the ratio of voting rewards ought to be 20:5. - // Note that compute_maturities returns the resulting maturities - // when 100 e8s of voting rewards are distributed. - assert_eq!( - compute_maturities(vec![stake, stake], vec!["P-G", "-PN", "-PN", "-PN", "-PN", "-PN"], USUAL_REWARD_POT_E8S), - vec![40, 10], - ); - // Make sure that, when voting on proposals of the same type in - // the ratio 1:4, they get voting rewards in the ratio 1:4. - assert_eq!( - compute_maturities(vec![stake, stake], vec!["P-N", "-PN", "-PN", "-PN", "-PN"], USUAL_REWARD_POT_E8S), - vec![10, 40], - ); - assert_eq!( - compute_maturities(vec![stake, stake], vec!["P-G", "-PG", "-PG", "-PG", "-PG"], USUAL_REWARD_POT_E8S), - vec![10, 40], - ); -} +fn test_topic_weights() { + for stake in [ + 1, + 2, + 3, + 10_000, + 20_000, + 30_000, + E8, + 2 * E8, + 3 * E8, + 100 * E8, + 200 * E8, + 300 * E8, + ] { + // Check that voting on + // 1. a governance proposal yields 20 times the voting power + // 3. other proposals yield 1 time the voting power + + // Test alloacting 100 maturity to two neurons with equal stake where + // 1. first neuron voting on a gov proposal (20x) and + // 2. second neuron voting on a network proposal (1x). + // Overall reward weights are 2 * (20+1) = 42 + // First neuron gets 20/42 * 100 = 47.61 truncated to 47. + // Second neuron gets 1/42 * 100 = 2.38 truncated to 2. + assert_eq!( + compute_maturities(vec![stake, stake], vec!["P-G", "-PN"], USUAL_REWARD_POT_E8S), + vec![47, 2], + "{stake}", + ); + // First neuron proposes and votes on a governance proposal. + // Second neuron proposes and votes on five network economics proposals. + // The first neuron receives 20x the voting power and + // the second neuron receives 5x the voting power. + // Thus, the ratio of voting rewards ought to be 20:5. + // Note that compute_maturities returns the resulting maturities + // when 100 e8s of voting rewards are distributed. + assert_eq!( + compute_maturities( + vec![stake, stake], + vec!["P-G", "-PN", "-PN", "-PN", "-PN", "-PN"], + USUAL_REWARD_POT_E8S + ), + vec![40, 10], + "{stake}", + ); + // Make sure that, when voting on proposals of the same type in + // the ratio 1:4, they get voting rewards in the ratio 1:4. + assert_eq!( + compute_maturities( + vec![stake, stake], + vec!["P-N", "-PN", "-PN", "-PN", "-PN"], + USUAL_REWARD_POT_E8S + ), + vec![10, 40], + "{stake}", + ); + assert_eq!( + compute_maturities( + vec![stake, stake], + vec!["P-G", "-PG", "-PG", "-PG", "-PG"], + USUAL_REWARD_POT_E8S + ), + vec![10, 40], + "{stake}", + ); + } } #[test] @@ -6394,7 +6414,7 @@ async fn test_staked_maturity() { // Neuron should get the maturity equivalent of 5 days as staked maturity. assert_eq!( neuron.staked_maturity_e8s_equivalent.unwrap(), - 54719555847781_u64 + 34_746_917_963_341, ); assert_eq!(neuron.maturity_e8s_equivalent, 0); @@ -6429,7 +6449,7 @@ async fn test_staked_maturity() { assert_eq!(neuron.maturity_e8s_equivalent, 0); assert_eq!( neuron.staked_maturity_e8s_equivalent, - Some(54719555847781_u64) + Some(34_746_917_963_341), ); // Configure the neuron to auto-stake any future maturity. @@ -6455,7 +6475,7 @@ async fn test_staked_maturity() { .neuron_store .with_neuron(&id, |neuron| neuron.clone()) .expect("Neuron not found"); - assert_eq!(neuron.maturity_e8s_equivalent, 54719555847781_u64); + assert_eq!(neuron.maturity_e8s_equivalent, 34_746_917_963_341); assert_eq!(neuron.staked_maturity_e8s_equivalent, None); } @@ -14339,3 +14359,19 @@ async fn test_grandfathering() { .contains(&NeuronId { id: 1 }) ); } + +// Warning: This might not deal with rounding errors correctly, but for inputs +// used so far, this works. +fn total_supply_for_target_voting_rewards_pot_at_genesis( + target_voting_rewards_pot: Tokens, +) -> Tokens { + let result = target_voting_rewards_pot.get_e8s() as f64 + * 365.25 + // The the reward rate started at 10% and goes down (quadratically) + // to 5% (prior to Mission 70 changes). + / 0.1 + // To compensate for the Mission 70 voting rewards adjustment. + / 0.635; + + Tokens::from_e8s(result.ceil() as u64) +} diff --git a/rs/nns/governance/tests/proposals.rs b/rs/nns/governance/tests/proposals.rs index 5116597b7cc2..fd8a73a02bf0 100644 --- a/rs/nns/governance/tests/proposals.rs +++ b/rs/nns/governance/tests/proposals.rs @@ -209,7 +209,7 @@ async fn test_distribute_rewards_with_total_potential_voting_power() { let fake_driver = fake::FakeDriver::default() .at(NOW_SECONDS) - .with_supply(Tokens::from_tokens(100).unwrap()); + .with_supply(Tokens::from_tokens(1_000_000).unwrap()); let governance_init = api::Governance { neurons: NEURONS.clone(), @@ -257,6 +257,7 @@ async fn test_distribute_rewards_with_total_potential_voting_power() { // Remember, this can return NEGATIVE. Most of the time, you want to do // assert!(e.abs() < EPSILON, ...). Do NOT forget the .abs() ! + #[track_caller] fn assert_ratio_relative_error_close( observed: (u64, u64), expected: (u64, u64), @@ -277,7 +278,7 @@ async fn test_distribute_rewards_with_total_potential_voting_power() { assert_ratio_relative_error_close( rewards, weighted_voting_powers, - 2e-6, + 3e-9, "rewards vs. weighted_voting_powers", ); @@ -285,13 +286,13 @@ async fn test_distribute_rewards_with_total_potential_voting_power() { assert_ratio_relative_error_close( (rewards.0, reward_event.total_available_e8s_equivalent), (weighted_voting_powers.0, (32_100 + 80_000 + 20 * 80_000)), - 2e-6, + 3e-9, "2nd neuron (ID = 1043)", ); assert_ratio_relative_error_close( (rewards.1, reward_event.total_available_e8s_equivalent), (weighted_voting_powers.1, (32_100 + 80_000 + 20 * 80_000)), - 2e-6, + 3e-9, "2nd neuron (ID = 1043)", ); } diff --git a/rs/nns/integration_tests/src/governance_neurons.rs b/rs/nns/integration_tests/src/governance_neurons.rs index 23674b59408c..a749b87f2b09 100644 --- a/rs/nns/integration_tests/src/governance_neurons.rs +++ b/rs/nns/integration_tests/src/governance_neurons.rs @@ -425,7 +425,7 @@ fn test_neuron_disburse_maturity() { let original_neuron_2_maturity_e8s_equivalent = neuron_2.maturity_e8s_equivalent; assert_eq!(neuron_2.maturity_disbursements_in_progress, Some(vec![])); - // Step 2.1: Disburse 30% of maturity for neuron 1 + // Step 2.1: Disburse 40% of maturity for neuron 1 let disburse_destination_1_principal = PrincipalId::new_self_authenticating(b"disburse_destination_1"); let disburse_destination_1 = AccountIdentifier::from(disburse_destination_1_principal); @@ -434,7 +434,7 @@ fn test_neuron_disburse_maturity() { neuron_1_controller, neuron_id_1, DisburseMaturity { - percentage_to_disburse: 30, + percentage_to_disburse: 40, to_account: Some(GovernanceAccount { owner: Some(disburse_destination_1_principal), subaccount: None, @@ -720,7 +720,7 @@ fn test_neuron_disburse_maturity_through_neuron_management_proposal() { .with_ledger_accounts(vec![ ( AccountIdentifier::new(managed_neuron_controller, None), - Tokens::new(1000, 10000).unwrap(), + Tokens::new(2_000, 10_000).unwrap(), ), ( AccountIdentifier::new(neuron_manager_controller, None), @@ -734,7 +734,7 @@ fn test_neuron_disburse_maturity_through_neuron_management_proposal() { let managed_neuron_id = create_neuron_with_maturity( &state_machine, managed_neuron_controller, - Tokens::from_tokens(1000).unwrap(), + Tokens::from_tokens(2_000).unwrap(), false, ); From 97a4167e531732c0d2d77482f152b075789a7f7b Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Wed, 1 Apr 2026 15:30:33 +0200 Subject: [PATCH 2/4] Fixed a test. --- rs/nns/governance/src/voting.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rs/nns/governance/src/voting.rs b/rs/nns/governance/src/voting.rs index 8b00489d928c..c8cfeb555974 100644 --- a/rs/nns/governance/src/voting.rs +++ b/rs/nns/governance/src/voting.rs @@ -595,6 +595,7 @@ mod test { use crate::test_utils::MockRandomness; use crate::{ governance::{Governance, REWARD_DISTRIBUTION_PERIOD_SECONDS}, + is_mission_70_voting_rewards_enabled, neuron::{DissolveStateAndAge, Neuron, NeuronBuilder}, neuron_store::NeuronStore, pb::v1::{ @@ -1394,7 +1395,18 @@ mod test { } #[tokio::test] - async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine() { + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_pre_mission_70() { + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(5474); + } + + #[tokio::test] + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_with_mission_70() { + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); + test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(3476); + } + + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(expected_neuron_1_maturity: u64) { let now = 1733433219; let topic = Topic::Governance; let environment = MockEnvironment::new( @@ -1530,7 +1542,7 @@ mod test { .neuron_store .with_neuron(&NeuronId { id: 1 }, |n| n.maturity_e8s_equivalent) .unwrap(), - 3476, + expected_neuron_1_maturity, ); } From 59d1424de03e1e6426e7c0efabea0dbb2f43827c Mon Sep 17 00:00:00 2001 From: IDX GitHub Automation Date: Wed, 1 Apr 2026 14:35:22 +0000 Subject: [PATCH 3/4] Automatically fixing code for linting and formatting issues --- rs/nns/governance/src/voting.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rs/nns/governance/src/voting.rs b/rs/nns/governance/src/voting.rs index c8cfeb555974..ed519d3ffe61 100644 --- a/rs/nns/governance/src/voting.rs +++ b/rs/nns/governance/src/voting.rs @@ -1395,18 +1395,22 @@ mod test { } #[tokio::test] - async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_pre_mission_70() { + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_pre_mission_70() + { let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(5474); } #[tokio::test] - async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_with_mission_70() { + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_with_mission_70() + { let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(3476); } - async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(expected_neuron_1_maturity: u64) { + async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine( + expected_neuron_1_maturity: u64, + ) { let now = 1733433219; let topic = Topic::Governance; let environment = MockEnvironment::new( From 6b53e6c525df8da1448654338c62c14fdeae8dc7 Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Thu, 2 Apr 2026 11:39:08 +0200 Subject: [PATCH 4/4] fix test --- rs/nns/governance/src/voting.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rs/nns/governance/src/voting.rs b/rs/nns/governance/src/voting.rs index ed519d3ffe61..54a87e04bcac 100644 --- a/rs/nns/governance/src/voting.rs +++ b/rs/nns/governance/src/voting.rs @@ -595,7 +595,6 @@ mod test { use crate::test_utils::MockRandomness; use crate::{ governance::{Governance, REWARD_DISTRIBUTION_PERIOD_SECONDS}, - is_mission_70_voting_rewards_enabled, neuron::{DissolveStateAndAge, Neuron, NeuronBuilder}, neuron_store::NeuronStore, pb::v1::{ @@ -603,6 +602,8 @@ mod test { VotingPowerEconomics, WaitForQuietState, proposal::Action, }, storage::with_voting_state_machines_mut, + temporarily_disable_mission_70_voting_rewards, + temporarily_enable_mission_70_voting_rewards, test_utils::{ ExpectedCallCanisterMethodCallArguments, MockEnvironment, StubCMC, StubIcpLedger, }, @@ -1397,15 +1398,15 @@ mod test { #[tokio::test] async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_pre_mission_70() { - let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); - test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(5474); + let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); + test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(5474).await; } #[tokio::test] async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine_with_mission_70() { - let _restore_on_drop = temporarily_disable_mission_70_voting_rewards(); - test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(3476); + let _restore_on_drop = temporarily_enable_mission_70_voting_rewards(); + test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(3476).await; } async fn test_rewards_distribution_is_blocked_on_votes_not_cast_in_state_machine(