diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5480842 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "kiroAgent.configureMCP": "Disabled" +} \ No newline at end of file diff --git a/contracts/shared_utils/src/lib.rs b/contracts/shared_utils/src/lib.rs index b3deb80..b3fecea 100644 --- a/contracts/shared_utils/src/lib.rs +++ b/contracts/shared_utils/src/lib.rs @@ -40,19 +40,9 @@ pub use emergency::EmergencyControl; pub use error_codes::{category, code, emit_error_event, message_for_code}; pub use errors::ErrorHelper; pub use events::Events; -pub use fees; pub use math::SafeMath; pub use pausable::Pausable; pub use rate_limiting::RateLimiter; pub use storage::Storage; pub use time::TimeUtils; pub use validation::Validation; -pub use error_codes::*; -pub use errors::*; -pub use events::*; -pub use math::*; -pub use pausable::*; -pub use rate_limiting::*; -pub use storage::*; -pub use time::*; -pub use validation::*; diff --git a/contracts/shared_utils/src/storage.rs b/contracts/shared_utils/src/storage.rs index fad2440..6cdc69a 100644 --- a/contracts/shared_utils/src/storage.rs +++ b/contracts/shared_utils/src/storage.rs @@ -146,7 +146,7 @@ impl Storage { #[cfg(test)] mod tests { use super::*; - use soroban_sdk::{contract, contractimpl}; + use soroban_sdk::{contract, contractimpl, symbol_short}; // Dummy contract used to provide a valid contract context for storage access #[contract] @@ -157,8 +157,22 @@ mod tests { pub fn stub() {} } + // ======================================================================== + // Initialization Flag Tests + // ======================================================================== + #[test] - fn test_initialization() { + fn test_is_initialized_returns_false_by_default() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + assert!(!Storage::is_initialized(&env)); + }); + } + + #[test] + fn test_set_initialized_marks_contract_as_initialized() { let env = Env::default(); let contract_id = env.register_contract(None, TestContract); @@ -166,15 +180,114 @@ mod tests { assert!(!Storage::is_initialized(&env)); Storage::set_initialized(&env); + assert!(Storage::is_initialized(&env)); }); } #[test] - fn test_admin_storage() { + fn test_set_initialized_is_idempotent() { let env = Env::default(); - let admin = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + assert!(Storage::is_initialized(&env)); + + // Setting initialized again should not cause issues + Storage::set_initialized(&env); + assert!(Storage::is_initialized(&env)); + }); + } + #[test] + fn test_require_initialized_succeeds_when_initialized() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + + // Should not panic + Storage::require_initialized(&env); + }); + } + + #[test] + #[should_panic(expected = "Contract not initialized")] + fn test_require_initialized_panics_when_not_initialized() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::require_initialized(&env); + }); + } + + #[test] + fn test_require_not_initialized_succeeds_when_not_initialized() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + // Should not panic + Storage::require_not_initialized(&env); + }); + } + + #[test] + #[should_panic(expected = "Contract already initialized")] + fn test_require_not_initialized_panics_when_initialized() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + Storage::require_not_initialized(&env); + }); + } + + #[test] + fn test_initialization_flag_persists_across_calls() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + // First call: set initialized + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + }); + + // Second call: verify it persists + env.as_contract(&contract_id, || { + assert!(Storage::is_initialized(&env)); + }); + } + + #[test] + fn test_initialization_flag_uses_correct_storage_key() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + + // Verify the key exists in storage + assert!(env.storage().instance().has(&keys::INITIALIZED)); + + // Verify the value is true + let value: bool = env.storage().instance().get(&keys::INITIALIZED).unwrap(); + assert_eq!(value, true); + }); + } + + // ======================================================================== + // Admin Storage Tests + // ======================================================================== + + #[test] + fn test_set_and_get_admin() { + let env = Env::default(); + let admin = ::generate(&env); let contract_id = env.register_contract(None, TestContract); env.as_contract(&contract_id, || { @@ -186,26 +299,352 @@ mod tests { }); } + #[test] + fn test_admin_can_be_updated() { + let env = Env::default(); + let admin1 = ::generate(&env); + let admin2 = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + + // Set first admin + Storage::set_admin(&env, &admin1); + assert_eq!(Storage::get_admin(&env), admin1); + + // Update to second admin + Storage::set_admin(&env, &admin2); + assert_eq!(Storage::get_admin(&env), admin2); + }); + } + #[test] #[should_panic(expected = "Contract not initialized")] - fn test_require_initialized_fails() { + fn test_get_admin_panics_when_not_initialized() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::get_admin(&env); + }); + } + + #[test] + #[should_panic(expected = "Admin not set")] + fn test_get_admin_panics_when_admin_not_set() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + // Admin not set yet + Storage::get_admin(&env); + }); + } + + #[test] + fn test_admin_persists_across_calls() { + let env = Env::default(); + let admin = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + // First call: set admin + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin); + }); + + // Second call: verify it persists + env.as_contract(&contract_id, || { + let stored_admin = Storage::get_admin(&env); + assert_eq!(stored_admin, admin); + }); + } + + #[test] + fn test_admin_uses_correct_storage_key() { + let env = Env::default(); + let admin = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin); + + // Verify the key exists in storage + assert!(env.storage().instance().has(&keys::ADMIN)); + + // Verify the value matches + let stored: Address = env.storage().instance().get(&keys::ADMIN).unwrap(); + assert_eq!(stored, admin); + }); + } + + #[test] + fn test_multiple_admins_in_different_contracts() { + let env = Env::default(); + let admin1 = ::generate(&env); + let admin2 = ::generate(&env); + + let contract_id1 = env.register_contract(None, TestContract); + let contract_id2 = env.register_contract(None, TestContract); + + // Set different admins for different contracts + env.as_contract(&contract_id1, || { + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin1); + }); + + env.as_contract(&contract_id2, || { + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin2); + }); + + // Verify each contract has its own admin + env.as_contract(&contract_id1, || { + assert_eq!(Storage::get_admin(&env), admin1); + }); + + env.as_contract(&contract_id2, || { + assert_eq!(Storage::get_admin(&env), admin2); + }); + } + + // ======================================================================== + // Generic Storage Helper Tests + // ======================================================================== + + #[test] + fn test_get_or_default_returns_default_when_key_not_exists() { let env = Env::default(); let contract_id = env.register_contract(None, TestContract); env.as_contract(&contract_id, || { + let key = symbol_short!("TESTKEY"); + let default_value = 42i128; + + let result = Storage::get_or_default(&env, &key, default_value); + assert_eq!(result, default_value); + }); + } + + #[test] + fn test_get_or_default_returns_stored_value_when_exists() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("TESTKEY"); + let stored_value = 100i128; + let default_value = 42i128; + + Storage::set(&env, &key, &stored_value); + + let result = Storage::get_or_default(&env, &key, default_value); + assert_eq!(result, stored_value); + }); + } + + #[test] + fn test_set_and_get_generic_value() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("TESTKEY"); + let value = 12345i128; + + Storage::set(&env, &key, &value); + + let result: Option = Storage::get(&env, &key); + assert_eq!(result, Some(value)); + }); + } + + #[test] + fn test_get_returns_none_when_key_not_exists() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("NOEXIST"); + + let result: Option = Storage::get(&env, &key); + assert_eq!(result, None); + }); + } + + #[test] + fn test_has_returns_false_when_key_not_exists() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("NOEXIST"); + + assert!(!Storage::has(&env, &key)); + }); + } + + #[test] + fn test_has_returns_true_when_key_exists() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("TESTKEY"); + let value = 999i128; + + Storage::set(&env, &key, &value); + + assert!(Storage::has(&env, &key)); + }); + } + + #[test] + fn test_storage_with_different_types() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + // Test with i128 + let key_i128 = symbol_short!("INT"); + Storage::set(&env, &key_i128, &12345i128); + assert_eq!(Storage::get::(&env, &key_i128), Some(12345i128)); + + // Test with u64 + let key_u64 = symbol_short!("UINT"); + Storage::set(&env, &key_u64, &67890u64); + assert_eq!(Storage::get::(&env, &key_u64), Some(67890u64)); + + // Test with bool + let key_bool = symbol_short!("BOOL"); + Storage::set(&env, &key_bool, &true); + assert_eq!(Storage::get::(&env, &key_bool), Some(true)); + }); + } + + #[test] + fn test_storage_key_isolation() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key1 = symbol_short!("KEY1"); + let key2 = symbol_short!("KEY2"); + + Storage::set(&env, &key1, &100i128); + Storage::set(&env, &key2, &200i128); + + // Verify keys are isolated + assert_eq!(Storage::get::(&env, &key1), Some(100i128)); + assert_eq!(Storage::get::(&env, &key2), Some(200i128)); + }); + } + + #[test] + fn test_storage_value_can_be_overwritten() { + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + let key = symbol_short!("TESTKEY"); + + Storage::set(&env, &key, &100i128); + assert_eq!(Storage::get::(&env, &key), Some(100i128)); + + Storage::set(&env, &key, &200i128); + assert_eq!(Storage::get::(&env, &key), Some(200i128)); + }); + } + + // ======================================================================== + // Storage Key Constants Tests + // ======================================================================== + + #[test] + fn test_storage_key_constants_are_unique() { + // Verify that ADMIN and INITIALIZED keys are different + assert_ne!(keys::ADMIN, keys::INITIALIZED); + } + + #[test] + fn test_storage_keys_are_short_symbols() { + // Verify keys are valid short symbols (max 9 characters) + // This is implicit in the symbol_short! macro usage + let env = Env::default(); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + // Should not panic - keys are valid + env.storage().instance().set(&keys::ADMIN, &true); + env.storage().instance().set(&keys::INITIALIZED, &true); + }); + } + + // ======================================================================== + // Integration Tests: Initialization + Admin Flow + // ======================================================================== + + #[test] + fn test_typical_initialization_flow() { + let env = Env::default(); + let admin = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + // Step 1: Verify not initialized + assert!(!Storage::is_initialized(&env)); + Storage::require_not_initialized(&env); + + // Step 2: Initialize and set admin + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin); + + // Step 3: Verify initialized + assert!(Storage::is_initialized(&env)); Storage::require_initialized(&env); + + // Step 4: Verify admin is set + assert_eq!(Storage::get_admin(&env), admin); }); } #[test] #[should_panic(expected = "Contract already initialized")] - fn test_require_not_initialized_fails() { + fn test_cannot_reinitialize_after_initialization() { let env = Env::default(); + let admin1 = ::generate(&env); + let admin2 = ::generate(&env); let contract_id = env.register_contract(None, TestContract); env.as_contract(&contract_id, || { + // First initialization + Storage::require_not_initialized(&env); Storage::set_initialized(&env); + Storage::set_admin(&env, &admin1); + + // Attempt second initialization should fail Storage::require_not_initialized(&env); + Storage::set_initialized(&env); + Storage::set_admin(&env, &admin2); + }); + } + + #[test] + fn test_admin_can_be_set_before_initialization() { + let env = Env::default(); + let admin = ::generate(&env); + let contract_id = env.register_contract(None, TestContract); + + env.as_contract(&contract_id, || { + // set_admin doesn't require initialization + Storage::set_admin(&env, &admin); + + // But get_admin does require initialization + Storage::set_initialized(&env); + assert_eq!(Storage::get_admin(&env), admin); }); } }