diff --git a/rs/nns/governance/src/neuron/types.rs b/rs/nns/governance/src/neuron/types.rs index 59c7a3e2042..276fbdd246d 100644 --- a/rs/nns/governance/src/neuron/types.rs +++ b/rs/nns/governance/src/neuron/types.rs @@ -1358,6 +1358,7 @@ impl Neuron { let deciding_voting_power = Some(self.deciding_voting_power(voting_power_economics, now_seconds)); let potential_voting_power = Some(self.potential_voting_power(now_seconds)); + let recent_ballots = self.sorted_recent_ballots(); let Neuron { id, @@ -1370,7 +1371,7 @@ impl Neuron { created_timestamp_seconds, spawn_at_timestamp_seconds, followees, - recent_ballots, + recent_ballots: _, kyc_verified, transfer, maturity_e8s_equivalent, diff --git a/rs/nns/governance/src/neuron/types/tests.rs b/rs/nns/governance/src/neuron/types/tests.rs index a9e0ca3c0af..804dfc0d2e6 100644 --- a/rs/nns/governance/src/neuron/types/tests.rs +++ b/rs/nns/governance/src/neuron/types/tests.rs @@ -10,14 +10,130 @@ use crate::{ temporarily_enable_private_neuron_enforcement, temporarily_enable_voting_power_adjustment, }; use ic_cdk::println; - use ic_nervous_system_common::{E8, ONE_MONTH_SECONDS, ONE_YEAR_SECONDS}; use icp_ledger::Subaccount; +use maplit::hashmap; +use pretty_assertions::assert_eq; const NOW: u64 = 123_456_789; const TWELVE_MONTHS_SECONDS: u64 = 30 * 12 * 24 * 60 * 60; +/// Our main goal here is to make sure that recent_ballots is populated +/// correctly. +#[test] +fn test_neuron_into_api() { + // Step 1: Prepare "the world". In this case, that just consists of + // constructing a native Neuron. + + let some_timestamp_seconds = 1738239721; + + let dissolve_state_and_age = DissolveStateAndAge::NotDissolving { + dissolve_delay_seconds: 100, + aging_since_timestamp_seconds: some_timestamp_seconds + 30, + }; + + let mut original_neuron = NeuronBuilder::new( + NeuronId { id: 7 }, + Subaccount::try_from(vec![8_u8; 32].as_slice()).unwrap(), + PrincipalId::new_user_test_id(9), + dissolve_state_and_age, + some_timestamp_seconds + 3, // created_timestamp_seconds + ) + .build(); + + // Add ballots. + for i in 0..10 { + let proposal_id = Some(ProposalId { id: 123_000 + i }); + + original_neuron.recent_ballots.push(pb::BallotInfo { + proposal_id, + vote: Vote::No as i32, + }); + }; + original_neuron.recent_ballots_next_entry_index = Some(3); + + // Step 2: run code under test. + let api_neuron = original_neuron.clone().into_api(some_timestamp_seconds + 99, &VotingPowerEconomics::DEFAULT); + + // Step 3: Inspect result(s). + + // Survives round trip. + assert_eq!( + Neuron { + // This is checked separately. + recent_ballots: vec![], + ..Neuron::try_from(api_neuron.clone()).unwrap() + }, + Neuron { + recent_ballots: vec![], + recent_ballots_next_entry_index: None, + visibility: Some(pb::Visibility::Private), + ..original_neuron.clone() + } + ); + + // Verify most fields, or at least, the interesting ones. + let potential_voting_power = Some(original_neuron.potential_voting_power(some_timestamp_seconds + 99)); + let deciding_voting_power = Some(original_neuron.deciding_voting_power(&VotingPowerEconomics::DEFAULT, some_timestamp_seconds + 99)); + assert_eq!( + api::Neuron { + recent_ballots: vec![], + ..api_neuron.clone() + }, + api::Neuron { + // Fields with "interesting" values. + id: Some(NeuronId { id: 7 }), + account: vec![8_u8; 32], + controller: Some(PrincipalId::new_user_test_id(9)), + dissolve_state: Some(api::neuron::DissolveState::DissolveDelaySeconds(100)), + aging_since_timestamp_seconds: some_timestamp_seconds + 30, + created_timestamp_seconds: some_timestamp_seconds + 3, + visibility: Some(Visibility::Private as i32), + voting_power_refreshed_timestamp_seconds: Some(some_timestamp_seconds + 3), + + // Other fields have default value. + hot_keys: vec![], + cached_neuron_stake_e8s: 0, + neuron_fees_e8s: 0, + spawn_at_timestamp_seconds: None, + followees: hashmap!{}, + recent_ballots: vec![], + kyc_verified: false, + transfer: None, + maturity_e8s_equivalent: 0, + staked_maturity_e8s_equivalent: None, + auto_stake_maturity: None, + not_for_profit: false, + joined_community_fund_timestamp_seconds: None, + known_neuron_data: None, + neuron_type: None, + potential_voting_power, + deciding_voting_power, + }, + ); + + // In particular, recent_ballots_next_entry_index is used (indirectly) during conversion. + let expected_proposal_ids = vec![ + 123_002, 123_001, 123_000, 123_009, 123_008, 123_007, 123_006, 123_005, 123_004, 123_003, + ]; + assert_eq!(expected_proposal_ids.len(), 10); + assert_eq!( + api_neuron.recent_ballots, + expected_proposal_ids + .into_iter() + .map(|id| { + let proposal_id = Some(ProposalId { id }); + + api::BallotInfo { + proposal_id, + vote: Vote::No as i32, + } + }) + .collect::>(), + ); +} + #[test] fn test_dissolve_state_and_age_conversion() { let test_cases = vec![