#[cfg(test)] mod security_tests { use crate::helpers::tests::*; use crate::{Contract, VaultConfig}; use near_contract_standards::fungible_token::FungibleTokenCore; use near_sdk::{json_types::U128, AccountId}; #[test] fn test_input_validation_asset_bounds() { setup_context(); let contract = create_test_contract(); // Test that large but safe amounts don't cause overflow // With 18 extra decimals, max safe amount is u128::MAX / 10^18 let max_safe_amount = u128::MAX / 10u128.pow(18); let test_amount = U128(max_safe_amount); // Should not panic or overflow during conversion let shares = contract.convert_to_shares(test_amount, contract.base_asset.clone()); let _back_to_assets = contract.convert_to_asset_amount(shares, contract.base_asset.clone()); // Basic validation that we get reasonable results assert!(shares.0 <= u128::MAX); } #[test] fn test_input_sanitization_account_ids() { setup_context(); // Test that invalid AccountIds are rejected properly let invalid_account_formats = vec![ "", "UPPERCASE", "invalid..dots", "invalid-.-format", "toolongaccountnamethatexceedsthe64characterlimitforaccountnames.near", ]; for invalid_format in invalid_account_formats { let result = AccountId::try_from(invalid_format.to_string()); // Should return error for invalid formats assert!( result.is_err(), "Expected '{}' to be invalid AccountId format", invalid_format ); } } #[test] fn test_numeric_bounds_protection() { setup_context(); let contract = create_test_contract(); // Test with zero amounts let zero_amount = U128(0); let zero_shares = contract.convert_to_shares(zero_amount, contract.base_asset.clone()); assert_eq!(zero_shares.0, 0); // Test that precision is maintained for small amounts let small_amount = U128(100); // Very small amount let small_shares = contract.convert_to_shares(small_amount, contract.base_asset.clone()); let recovered_amount = contract.convert_to_asset_amount(small_shares, contract.base_asset.clone()); // Should be reasonably close due to rounding if small_shares.0 > 0 { assert!(recovered_amount.0 <= small_amount.0 * 2); // Allow for some precision loss } } #[test] fn test_state_consistency_validation() { setup_context(); let contract = create_test_contract(); // Test that contract state is internally consistent // Share price should be positive let share_price = contract.get_share_price(contract.base_asset.clone()); assert!(share_price.0 > 0, "Share price must be positive"); // Total supply should start at zero assert_eq!(contract.ft_total_supply().0, 0); // Vault balances should be consistent let vault_balance = contract.internal_get_vault_balance(&contract.base_asset); assert_eq!(vault_balance.available_assets.0, 0); // Should start at zero assert_eq!(vault_balance.pending_deposit.0, 0); // Should start at zero } #[test] fn test_access_control_boundary_basic() { setup_context(); let contract = create_test_contract(); let unauthorized_user = bob(); // Test that alice (deployer) has owner role assert!(contract.internal_has_role(&alice(), "owner")); // Test that random user does not have owner role assert!(!contract.internal_has_role(&unauthorized_user, "owner")); // Test that owner policies are properly initialized let owner_policy = contract.get_policy_by_id("grant_role".to_string()); assert!(owner_policy.is_some()); let policy = owner_policy.unwrap(); assert_eq!(policy.required_role, "owner"); assert_eq!(policy.required_vote_count, 1); } #[test] fn test_configuration_bounds_validation() { setup_context(); // Test that extreme configuration values are handled properly let config = VaultConfig { tvl_cap: Some(U128(u128::MAX)), // Maximum possible cap min_deposit: U128(0), // Minimum deposit min_withdraw: U128(0), // Minimum withdrawal management_fee_bps: 10000, // Maximum fee (100%) performance_fee_bps: 10000, // Maximum fee (100%) deposit_is_async: true, redeem_is_async: true, ..VaultConfig::default() }; // Should be able to create contract with extreme but valid config let _contract = Contract::new_with_config( asset_account(), test_base_asset(), USDC_DECIMALS, EXTRA_DECIMALS, "TEST".to_string(), "Test Vault".to_string(), vec![test_base_asset()], config, None, None, ); // If we reach here, extreme values didn't cause panic } #[test] fn test_asset_validation_boundaries() { setup_context(); let contract = create_test_contract(); // Test with the base asset (should be valid) contract.internal_validate_asset_accepted(&contract.base_asset); // Should not panic // Test asset consistency assert!(contract.accepted_assets.contains(&contract.base_asset)); assert_eq!(contract.asset(), contract.base_asset); } #[test] fn test_time_manipulation_resistance_basic() { // Test basic timestamp handling setup_context(); let contract = create_test_contract(); // Test that contract operations don't rely on manipulable timestamps // for basic functionality let _share_price = contract.get_share_price(contract.base_asset.clone()); // Test that basic operations work regardless of block timestamp } #[test] fn test_integer_overflow_protection() { setup_context(); let contract = create_test_contract(); // Test operations with large numbers that are within safe bounds // With 18 extra decimals, we need to respect the conversion bounds let max_safe_amount = u128::MAX / 10u128.pow(18); let large_amount = U128(max_safe_amount / 1000); // Large but safe // These operations should not panic due to overflow let shares = contract.convert_to_shares(large_amount, contract.base_asset.clone()); let _assets_back = contract.convert_to_asset_amount(shares, contract.base_asset.clone()); // Should handle large numbers gracefully assert!(shares.0 < u128::MAX); // Should not overflow } #[test] fn test_precision_attack_resistance() { setup_context(); let contract = create_test_contract(); // Test that very small amounts don't cause precision issues // that could be exploited (dust attacks, etc.) for i in 1..=10 { let small_amount = U128(i); let shares = contract.convert_to_shares(small_amount, contract.base_asset.clone()); // Should either give reasonable shares or zero (not negative or overflow) assert!(shares.0 < u128::MAX); if shares.0 > 0 { let back_to_assets = contract.convert_to_asset_amount(shares, contract.base_asset.clone()); // Should be reasonable (not massively different) assert!(back_to_assets.0 <= small_amount.0 * 1000); // Allow significant precision loss for tiny amounts } } } #[test] fn test_denial_of_service_resistance() { setup_context(); let contract = create_test_contract(); // Test that expensive operations don't consume excessive gas // by testing operations that could potentially be expensive let test_amount = U128(1_000_000_000); // 1000 tokens // These operations should complete in reasonable time/gas for _ in 0..100 { let _shares = contract.convert_to_shares(test_amount, contract.base_asset.clone()); } // If we complete the loop without timeout, DOS resistance is good } }