#[cfg(test)] mod test_emergency_system { use crate::permission::types::*; use crate::*; use common_data::constants::NANOSECONDS_PER_WEEK; use near_sdk::test_utils::{accounts, VMContextBuilder}; use near_sdk::testing_env; // Test constants const EMERGENCY_INACTIVE_DURATION: u128 = NANOSECONDS_PER_WEEK * 2; // 2 weeks const EMERGENCY_VOTE_DURATION: u128 = NANOSECONDS_PER_WEEK; // 1 week const EMERGENCY_VOTE_THRESHOLD: u32 = 3; fn setup_context_with_time(predecessor: near_sdk::AccountId, block_timestamp: u64) { let mut context = VMContextBuilder::new(); context .predecessor_account_id(predecessor.clone()) .signer_account_id(predecessor) .block_timestamp(block_timestamp); testing_env!(context.build()); } fn current_timestamp() -> u64 { 1_000_000_000_000_000_000 // 1 billion nanoseconds } /// Create a test contract with emergency system configured fn create_emergency_contract() -> Contract { setup_context_with_time(accounts(0), current_timestamp()); let emergency_accounts = vec![accounts(1), accounts(2), accounts(3), accounts(4)]; let mut contract = Contract::new( ChangeControl::default(), Some(accounts(0)), U128(EMERGENCY_INACTIVE_DURATION), emergency_accounts, EMERGENCY_VOTE_THRESHOLD, U128(EMERGENCY_VOTE_DURATION), ); // Grant some additional roles for testing (using only available accounts 0-5) contract.internal_grant_role("teller".to_string(), RoleTarget::AccountId(accounts(5))); contract.internal_grant_role( "accountant".to_string(), RoleTarget::AccountId("accountant.near".parse().unwrap()), ); contract.internal_grant_role( "strategist".to_string(), RoleTarget::AccountId("strategist.near".parse().unwrap()), ); contract } /// Create contract with additional policies for pagination testing fn create_emergency_contract_with_many_policies() -> Contract { let mut contract = create_emergency_contract(); // Add more policies beyond the default ones for i in 0..150 { let policy = Policy { id: format!("test_policy_{}", i), description: Some(format!("Test policy {}", i)), required_role: "strategist".to_string(), required_vote_count: 2, policy_type: PolicyType::KernelConfiguration, policy_details: PolicyDetails::KernelConfiguration, activation_time: U128(0), proposal_expiry_time_nanosec: U128(DEFAULT_PROPOSAL_EXPIRY_TIME_NS), required_pending_actions: vec![], }; contract .kernel_state .policies .insert(policy.id.clone(), policy); } contract } // ==================== vote_for_emergency_mode Tests ==================== #[test] fn test_vote_for_emergency_mode_success() { let mut contract = create_emergency_contract(); let emergency_account = accounts(1); // Vote from emergency account setup_context_with_time(emergency_account.clone(), current_timestamp()); contract.vote_for_emergency_mode(); // Verify vote was recorded let votes = contract.get_emergency_votes(); assert_eq!(votes.len(), 1, "Should have 1 emergency vote"); assert_eq!(votes[0].voter, emergency_account); assert_eq!(votes[0].timestamp.0, current_timestamp() as u128); } #[test] fn test_vote_for_emergency_mode_multiple_voters() { let mut contract = create_emergency_contract(); // Vote from multiple emergency accounts for i in 1..=3 { setup_context_with_time(accounts(i), current_timestamp()); contract.vote_for_emergency_mode(); } // Verify all votes were recorded let votes = contract.get_emergency_votes(); assert_eq!(votes.len(), 3, "Should have 3 emergency votes"); // Verify each voter is unique assert_eq!(votes[0].voter, accounts(1)); assert_eq!(votes[1].voter, accounts(2)); assert_eq!(votes[2].voter, accounts(3)); } #[test] fn test_vote_for_emergency_mode_duplicate_vote_replaces() { let mut contract = create_emergency_contract(); let emergency_account = accounts(1); // First vote setup_context_with_time(emergency_account.clone(), current_timestamp()); contract.vote_for_emergency_mode(); // Second vote from same account (should replace) let later_timestamp = current_timestamp() + 1_000_000_000; setup_context_with_time(emergency_account.clone(), later_timestamp); contract.vote_for_emergency_mode(); // Should still have only 1 vote, but with updated timestamp let votes = contract.get_emergency_votes(); assert_eq!(votes.len(), 1, "Should have 1 emergency vote (replaced)"); assert_eq!(votes[0].timestamp.0, later_timestamp as u128); } #[test] #[should_panic(expected = "Only emergency accounts can vote for emergency mode")] fn test_vote_for_emergency_mode_non_emergency_account() { let mut contract = create_emergency_contract(); let non_emergency_account = "attacker.near".parse().unwrap(); // Try to vote from non-emergency account setup_context_with_time(non_emergency_account, current_timestamp()); contract.vote_for_emergency_mode(); } #[test] fn test_vote_for_emergency_mode_before_inactive() { let mut contract = create_emergency_contract(); // Vote while kernel is still active setup_context_with_time(accounts(1), current_timestamp()); contract.vote_for_emergency_mode(); // Verify vote was recorded (voting is allowed before inactivity) let votes = contract.get_emergency_votes(); assert_eq!(votes.len(), 1, "Vote should be allowed before inactivity"); } #[test] fn test_get_active_emergency_votes_filters_expired() { let mut contract = create_emergency_contract(); let base_time = current_timestamp(); // Create votes at different times setup_context_with_time(accounts(1), base_time); contract.vote_for_emergency_mode(); setup_context_with_time(accounts(2), base_time + 1_000_000_000); contract.vote_for_emergency_mode(); // Move time forward beyond vote duration for BOTH votes let expired_time = base_time + 1_000_000_000 + (EMERGENCY_VOTE_DURATION as u64) + 1; setup_context_with_time(accounts(0), expired_time); // All votes should be expired now let active_votes = contract.get_active_emergency_votes(); assert_eq!(active_votes.len(), 0, "All votes should be expired"); // But get_emergency_votes should still return them let all_votes = contract.get_emergency_votes(); assert_eq!(all_votes.len(), 2, "All votes should still be stored"); } #[test] fn test_get_active_emergency_votes_within_duration() { let mut contract = create_emergency_contract(); // Create votes setup_context_with_time(accounts(1), current_timestamp()); contract.vote_for_emergency_mode(); setup_context_with_time(accounts(2), current_timestamp() + 1_000_000_000); contract.vote_for_emergency_mode(); // Check active votes before expiry setup_context_with_time(accounts(0), current_timestamp() + 1_000_000_000); let active_votes = contract.get_active_emergency_votes(); assert_eq!(active_votes.len(), 2, "Both votes should be active"); } // ==================== start_emergency_mode Tests ==================== #[test] #[should_panic(expected = "Insufficient emergency votes")] fn test_start_emergency_mode_insufficient_votes() { let mut contract = create_emergency_contract(); // Only 2 votes (threshold is 3) setup_context_with_time(accounts(1), current_timestamp()); contract.vote_for_emergency_mode(); setup_context_with_time(accounts(2), current_timestamp()); contract.vote_for_emergency_mode(); // Move time forward to make kernel inactive let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; setup_context_with_time(accounts(1), inactive_time); // Try to execute with insufficient votes (as emergency account) contract.start_emergency_mode(); } #[test] #[should_panic( expected = "Emergency mode cannot be activated yet: insufficient inactivity duration" )] fn test_start_emergency_mode_insufficient_inactivity() { let mut contract = create_emergency_contract(); // Get enough votes for i in 1..=3 { setup_context_with_time(accounts(i), current_timestamp()); contract.vote_for_emergency_mode(); } // Try to execute before inactive duration (as emergency account) setup_context_with_time(accounts(1), current_timestamp() + 1_000_000_000); contract.start_emergency_mode(); } #[test] #[should_panic(expected = "Only emergency accounts can execute emergency mode")] fn test_start_emergency_mode_non_emergency_account() { let mut contract = create_emergency_contract(); // Get enough votes let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; for i in 1..=3 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } // Try to execute from non-emergency account setup_context_with_time(accounts(0), inactive_time); contract.start_emergency_mode(); } #[test] fn test_start_emergency_mode_success() { let mut contract = create_emergency_contract(); // Move time forward to make kernel inactive first let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; setup_context_with_time(accounts(0), inactive_time); // Get enough votes (after kernel becomes inactive, so votes don't expire) for i in 1..=3 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } // Verify kernel is inactive assert!(contract.is_kernel_inactive(), "Kernel should be inactive"); // Execute emergency mode (as emergency account) setup_context_with_time(accounts(1), inactive_time); contract.start_emergency_mode(); // Verify emergency mode is active assert!( contract.kernel_state.emergency_mode_active, "Emergency mode should be active" ); // Verify emergency accounts have owner role for i in 1..=4 { assert!( contract.internal_has_role(&accounts(i), "owner"), "Emergency account {} should have owner role", i ); } // Verify all policies are reset to owner role with emergency threshold let policy = contract.get_policy_by_id("grant_role".to_string()).unwrap(); assert_eq!(policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD); assert_eq!(policy.required_role, "owner"); // Verify old roles are cleared assert!( !contract.internal_has_role(&accounts(5), "teller"), "Old teller role should be cleared" ); assert!( !contract.internal_has_role(&"accountant.near".parse().unwrap(), "accountant"), "Old accountant role should be cleared" ); } #[test] #[should_panic(expected = "Insufficient emergency votes")] fn test_start_emergency_mode_expired_votes() { let mut contract = create_emergency_contract(); // Create votes for i in 1..=3 { setup_context_with_time(accounts(i), current_timestamp()); contract.vote_for_emergency_mode(); } // Move time forward beyond both inactive duration AND vote duration let expired_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + (EMERGENCY_VOTE_DURATION as u64) + 1; setup_context_with_time(accounts(1), expired_time); // Should fail because votes are expired contract.start_emergency_mode(); } // ==================== set_policies_to_emergency_mode Tests ==================== #[test] fn test_set_policies_to_emergency_mode_success() { let mut contract = create_emergency_contract_with_many_policies(); // First activate emergency mode let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; for i in 1..=3 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } setup_context_with_time(accounts(1), inactive_time); contract.start_emergency_mode(); // Now set additional policies setup_context_with_time(accounts(1), inactive_time); contract.set_policies_to_emergency_mode(100, 150); // Verify policies in range are set let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); for i in 100..150.min(policy_ids.len()) { let policy = contract.get_policy_by_id(policy_ids[i].clone()).unwrap(); assert_eq!(policy.required_role, "owner"); assert_eq!(policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD); } } #[test] #[should_panic(expected = "Emergency mode is not active")] fn test_set_policies_to_emergency_mode_not_active() { let mut contract = create_emergency_contract_with_many_policies(); // Try to set policies without activating emergency mode first setup_context_with_time(accounts(1), current_timestamp()); contract.set_policies_to_emergency_mode(0, 50); } #[test] #[should_panic(expected = "Only emergency accounts can execute emergency mode")] fn test_set_policies_to_emergency_mode_non_emergency_account() { let mut contract = create_emergency_contract_with_many_policies(); // First activate emergency mode let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; for i in 1..=3 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } setup_context_with_time(accounts(1), inactive_time); contract.start_emergency_mode(); // Try to set policies from non-emergency account setup_context_with_time(accounts(0), inactive_time); contract.set_policies_to_emergency_mode(0, 50); } #[test] fn test_is_kernel_inactive() { let contract = create_emergency_contract(); // Initially not inactive (just created) setup_context_with_time(accounts(0), current_timestamp()); assert!( !contract.is_kernel_inactive(), "Should not be inactive initially" ); // Move time forward but not enough let not_yet_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) - 1; setup_context_with_time(accounts(0), not_yet_time); assert!(!contract.is_kernel_inactive(), "Should not be inactive yet"); // Move time forward past inactive duration let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; setup_context_with_time(accounts(0), inactive_time); assert!(contract.is_kernel_inactive(), "Should be inactive now"); } // ==================== internal_emergency_role_settings Tests ==================== #[test] fn test_internal_emergency_role_settings_clears_all_roles() { let mut contract = create_emergency_contract(); // Verify initial roles exist assert!(contract.internal_has_role(&accounts(0), "owner")); assert!(contract.internal_has_role(&accounts(5), "teller")); assert!(contract.internal_has_role(&"accountant.near".parse().unwrap(), "accountant")); // Execute emergency role reset contract.internal_emergency_role_settings(); // Verify all non-emergency accounts lost their roles assert!( !contract.internal_has_role(&accounts(0), "owner"), "Original owner should lose owner role" ); assert!(!contract.internal_has_role(&accounts(5), "teller")); assert!(!contract.internal_has_role(&"accountant.near".parse().unwrap(), "accountant")); assert!(!contract.internal_has_role(&"strategist.near".parse().unwrap(), "strategist")); } #[test] fn test_internal_emergency_role_settings_grants_emergency_accounts() { let mut contract = create_emergency_contract(); // Emergency accounts initially don't have owner role assert!(!contract.internal_has_role(&accounts(1), "owner")); assert!(!contract.internal_has_role(&accounts(2), "owner")); assert!(!contract.internal_has_role(&accounts(3), "owner")); assert!(!contract.internal_has_role(&accounts(4), "owner")); // Execute emergency role reset contract.internal_emergency_role_settings(); // Verify all emergency accounts have owner role assert!(contract.internal_has_role(&accounts(1), "owner")); assert!(contract.internal_has_role(&accounts(2), "owner")); assert!(contract.internal_has_role(&accounts(3), "owner")); assert!(contract.internal_has_role(&accounts(4), "owner")); } #[test] fn test_internal_emergency_role_settings_complete_reset() { let mut contract = create_emergency_contract(); // Execute emergency role reset contract.internal_emergency_role_settings(); // Verify total role assignments let all_assignments = contract.get_all_role_assignments(); // Should have exactly 4 entries (one for each emergency account) assert_eq!( all_assignments.len(), 4, "Should have 4 role assignments (emergency accounts only)" ); // Each should have exactly the owner role for (target, roles) in all_assignments { match target { RoleTarget::AccountId(account) => { assert!( contract .emergency_config .emergency_accounts .contains(&account), "Only emergency accounts should have roles" ); assert_eq!(roles.len(), 1, "Should have exactly 1 role"); assert_eq!(roles[0], "owner", "Should have owner role"); } _ => panic!("Should only have AccountId targets"), } } } // ==================== internal_emergency_policy_settings Tests ==================== #[test] fn test_internal_emergency_policy_settings_default_pagination() { let mut contract = create_emergency_contract(); // Get initial policy count let policy_count = contract.get_policy_count() as usize; // Execute with default pagination (0-100) contract.internal_emergency_policy_settings(None, None); // Verify all policies (within range) are set to owner role with emergency threshold let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); let expected_end = policy_count.min(100); for i in 0..expected_end { let policy = contract.get_policy_by_id(policy_ids[i].clone()); assert!(policy.is_some()); let policy = policy.unwrap(); assert_eq!( policy.required_role, "owner", "Policy should require owner role" ); assert_eq!( policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD, "Policy should require emergency threshold votes" ); } } #[test] fn test_internal_emergency_policy_settings_custom_pagination() { let mut contract = create_emergency_contract_with_many_policies(); // Get policy IDs let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); // Set some policies to non-owner role first for i in 10..20 { if let Some(policy) = contract.kernel_state.policies.get_mut(&policy_ids[i]) { policy.required_role = "strategist".to_string(); policy.required_vote_count = 5; } } // Execute with custom pagination (10-20) contract.internal_emergency_policy_settings(Some(10), Some(20)); // Verify policies in range are reset for i in 10..20 { let policy = contract.get_policy_by_id(policy_ids[i].clone()); assert!(policy.is_some()); let policy = policy.unwrap(); assert_eq!( policy.required_role, "owner", "Policy {} should require owner role", i ); assert_eq!( policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD, "Policy {} should require emergency threshold", i ); } // Verify policies outside range are unchanged let policy_before = contract.get_policy_by_id(policy_ids[5].clone()); assert!(policy_before.is_some()); // Verify policies after range if policy_ids.len() > 25 { let policy_after = contract.get_policy_by_id(policy_ids[25].clone()); assert!(policy_after.is_some()); } } #[test] fn test_internal_emergency_policy_settings_large_range() { let mut contract = create_emergency_contract_with_many_policies(); let policy_count = contract.kernel_state.policies.keys().count(); let max_index = policy_count.min(150); // Execute with large range (0-150, or up to policy count) contract.internal_emergency_policy_settings(Some(0), Some(max_index)); // Verify policies in the processed range are reset let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); for policy_id in policy_ids.iter().take(max_index) { let policy = contract.get_policy_by_id(policy_id.clone()); assert!(policy.is_some()); let policy = policy.unwrap(); assert_eq!( policy.required_role, "owner", "Policy {} should have owner role", policy_id ); assert_eq!( policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD, "Policy {} should have emergency threshold", policy_id ); } } #[test] #[should_panic(expected = "start_index must be less than end_index")] fn test_internal_emergency_policy_settings_invalid_range_equal() { let mut contract = create_emergency_contract(); // Try with start_index == end_index contract.internal_emergency_policy_settings(Some(10), Some(10)); } #[test] #[should_panic(expected = "start_index must be less than end_index")] fn test_internal_emergency_policy_settings_invalid_range_reversed() { let mut contract = create_emergency_contract(); // Try with start_index > end_index contract.internal_emergency_policy_settings(Some(20), Some(10)); } #[test] #[should_panic(expected = "start_index out of bounds")] fn test_internal_emergency_policy_settings_start_out_of_bounds() { let mut contract = create_emergency_contract(); let policy_count = contract.kernel_state.policies.keys().count(); // Try with start_index beyond policy count contract .internal_emergency_policy_settings(Some(policy_count + 10), Some(policy_count + 20)); } #[test] fn test_internal_emergency_policy_settings_end_beyond_count() { let mut contract = create_emergency_contract(); let policy_count = contract.kernel_state.policies.keys().count(); // Execute with end_index beyond policy count (should just process available policies) contract.internal_emergency_policy_settings(Some(0), Some(policy_count + 100)); // Verify all policies are reset let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); for policy_id in policy_ids { let policy = contract.get_policy_by_id(policy_id); assert!(policy.is_some()); let policy = policy.unwrap(); assert_eq!(policy.required_role, "owner"); assert_eq!(policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD); } } // ==================== Integration Tests ==================== #[test] fn test_emergency_mode_full_flow() { let mut contract = create_emergency_contract(); // Step 1: Verify initial state assert!(contract.internal_has_role(&accounts(0), "owner")); assert!(!contract.internal_has_role(&accounts(1), "owner")); assert!(!contract.is_kernel_inactive()); assert!(!contract.kernel_state.emergency_mode_active); // Step 2: Wait for inactivity first let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; setup_context_with_time(accounts(0), inactive_time); assert!(contract.is_kernel_inactive(), "Kernel should be inactive"); // Step 3: Emergency accounts vote (after inactivity to keep votes fresh) for i in 1..=4 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } let active_votes = contract.get_active_emergency_votes(); assert_eq!(active_votes.len(), 4, "Should have 4 active votes"); // Step 4: Execute emergency mode (as emergency account) setup_context_with_time(accounts(1), inactive_time); contract.start_emergency_mode(); // Step 5: Verify emergency state assert!( contract.kernel_state.emergency_mode_active, "Emergency mode should be active" ); // All emergency accounts should have owner role for i in 1..=4 { assert!(contract.internal_has_role(&accounts(i), "owner")); } // Original owner should lose role assert!(!contract.internal_has_role(&accounts(0), "owner")); // All policies should be reset let policy = contract.get_policy_by_id("grant_role".to_string()); assert!(policy.is_some()); let policy = policy.unwrap(); assert_eq!(policy.required_role, "owner"); assert_eq!(policy.required_vote_count, EMERGENCY_VOTE_THRESHOLD); } #[test] fn test_emergency_mode_with_pagination() { let mut contract = create_emergency_contract_with_many_policies(); // Wait for inactivity first let inactive_time = current_timestamp() + (EMERGENCY_INACTIVE_DURATION as u64) + 1; setup_context_with_time(accounts(0), inactive_time); // Vote after inactivity to keep votes fresh for i in 1..=3 { setup_context_with_time(accounts(i), inactive_time); contract.vote_for_emergency_mode(); } // Execute emergency mode for first batch (0-50) via start_emergency_mode setup_context_with_time(accounts(1), inactive_time); contract.start_emergency_mode(); // Verify first 100 policies are reset (default pagination) let policy_ids: Vec = contract.kernel_state.policies.keys().cloned().collect(); for i in 0..100.min(policy_ids.len()) { let policy = contract.get_policy_by_id(policy_ids[i].clone()); assert!(policy.is_some()); assert_eq!( policy.unwrap().required_vote_count, EMERGENCY_VOTE_THRESHOLD ); } // Set remaining policies setup_context_with_time(accounts(1), inactive_time); contract.set_policies_to_emergency_mode(100, 150); // Verify policies 100-150 are also reset for i in 100..150.min(policy_ids.len()) { let policy = contract.get_policy_by_id(policy_ids[i].clone()); assert!(policy.is_some()); assert_eq!( policy.unwrap().required_vote_count, EMERGENCY_VOTE_THRESHOLD ); } } }