diff --git a/contracts/predictify-hybrid/src/event_archive.rs b/contracts/predictify-hybrid/src/event_archive.rs index 4627f75..8e65cdc 100644 --- a/contracts/predictify-hybrid/src/event_archive.rs +++ b/contracts/predictify-hybrid/src/event_archive.rs @@ -427,6 +427,8 @@ mod tests { use super::*; use alloc::string::ToString; use soroban_sdk::testutils::Address as _; + use soroban_sdk::Map; + use crate::types::{Market, MarketState, OracleConfig}; struct EventArchiveTest { env: Env, @@ -749,4 +751,145 @@ mod tests { }); assert_eq!(entries.len(), 0); } + + #[test] + fn test_archive_max_length_market_id_no_panic() { + let test = EventArchiveTest::new(); + let env = &test.env; + let contract_id = env.register(crate::PredictifyHybrid, ()); + + env.as_contract(&contract_id, || { + let max_length_id = Symbol::new(env, "a_very_long_market_id_32_chars__"); + + let market = Market { + admin: test.admin.clone(), + question: String::from_str(env, "Will BTC reach 100k?"), + outcomes: soroban_sdk::vec![env, String::from_str(env, "yes")], + end_time: env.ledger().timestamp() + 3600, + oracle_config: OracleConfig::none_sentinel(env), + has_fallback: false, + fallback_oracle_config: OracleConfig::none_sentinel(env), + resolution_timeout: 3600, + oracle_result: None, + votes: Map::new(env), + total_staked: 0, + dispute_stakes: Map::new(env), + stakes: Map::new(env), + claimed: Map::new(env), + winning_outcomes: None, + fee_collected: false, + state: MarketState::Resolved, + total_extension_days: 0, + max_extension_days: 30, + extension_history: soroban_sdk::vec![env], + category: None, + tags: soroban_sdk::vec![env], + min_pool_size: None, + bet_deadline: 0, + dispute_window_seconds: 3600, + winnings_swept: false, + }; + + let res = crate::storage::StorageOptimizer::archive_market_data(env, &max_length_id, &market); + assert!(res.is_ok()); + }); + } + + #[test] + fn test_archive_repeated_same_ledger_works() { + let test = EventArchiveTest::new(); + let env = &test.env; + let contract_id = env.register(crate::PredictifyHybrid, ()); + + env.as_contract(&contract_id, || { + let market_id = Symbol::new(env, "market_1"); + + let market = Market { + admin: test.admin.clone(), + question: String::from_str(env, "Will BTC reach 100k?"), + outcomes: soroban_sdk::vec![env, String::from_str(env, "yes")], + end_time: env.ledger().timestamp() + 3600, + oracle_config: OracleConfig::none_sentinel(env), + has_fallback: false, + fallback_oracle_config: OracleConfig::none_sentinel(env), + resolution_timeout: 3600, + oracle_result: None, + votes: Map::new(env), + total_staked: 0, + dispute_stakes: Map::new(env), + stakes: Map::new(env), + claimed: Map::new(env), + winning_outcomes: None, + fee_collected: false, + state: MarketState::Resolved, + total_extension_days: 0, + max_extension_days: 30, + extension_history: soroban_sdk::vec![env], + category: None, + tags: soroban_sdk::vec![env], + min_pool_size: None, + bet_deadline: 0, + dispute_window_seconds: 3600, + winnings_swept: false, + }; + + let res1 = crate::storage::StorageOptimizer::archive_market_data(env, &market_id, &market); + assert!(res1.is_ok()); + + let res2 = crate::storage::StorageOptimizer::archive_market_data(env, &market_id, &market); + assert!(res2.is_ok()); + }); + } + + #[test] + fn test_archive_entries_are_retrievable() { + let test = EventArchiveTest::new(); + let env = &test.env; + let contract_id = env.register(crate::PredictifyHybrid, ()); + + env.as_contract(&contract_id, || { + let market_id = Symbol::new(env, "retrievable_market"); + let timestamp = env.ledger().timestamp(); + + let market = Market { + admin: test.admin.clone(), + question: String::from_str(env, "Will BTC reach 100k?"), + outcomes: soroban_sdk::vec![env, String::from_str(env, "yes")], + end_time: env.ledger().timestamp() + 3600, + oracle_config: OracleConfig::none_sentinel(env), + has_fallback: false, + fallback_oracle_config: OracleConfig::none_sentinel(env), + resolution_timeout: 3600, + oracle_result: None, + votes: Map::new(env), + total_staked: 0, + dispute_stakes: Map::new(env), + stakes: Map::new(env), + claimed: Map::new(env), + winning_outcomes: None, + fee_collected: false, + state: MarketState::Resolved, + total_extension_days: 0, + max_extension_days: 30, + extension_history: soroban_sdk::vec![env], + category: None, + tags: soroban_sdk::vec![env], + min_pool_size: None, + bet_deadline: 0, + dispute_window_seconds: 3600, + winnings_swept: false, + }; + + let res = crate::storage::StorageOptimizer::archive_market_data(env, &market_id, &market); + assert!(res.is_ok()); + + let key = crate::storage::DataKey::ArchivedMarket(market_id.clone(), timestamp); + let retrieved: Option = env.storage().persistent().get(&key); + assert!(retrieved.is_some()); + + let retrieved_market = retrieved.unwrap(); + assert_eq!(retrieved_market.question, market.question); + assert_eq!(retrieved_market.admin, market.admin); + }); + } } diff --git a/contracts/predictify-hybrid/src/storage.rs b/contracts/predictify-hybrid/src/storage.rs index d420d8e..cfb7bc1 100644 --- a/contracts/predictify-hybrid/src/storage.rs +++ b/contracts/predictify-hybrid/src/storage.rs @@ -22,6 +22,15 @@ enum StorageTtlTier { // ===== STORAGE OPTIMIZATION TYPES ===== +/// Storage key variants for contracts/predictify-hybrid +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + Whitelisted(Address), + Blacklisted(Address), + ArchivedMarket(Symbol, u64), +} + /// Storage format version for migration tracking #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] @@ -684,12 +693,9 @@ impl StorageOptimizer { } /// Archive market data before deletion - fn archive_market_data(env: &Env, market_id: &Symbol, market: &Market) -> Result<(), Error> { + pub(crate) fn archive_market_data(env: &Env, market_id: &Symbol, market: &Market) -> Result<(), Error> { // Store archived version with timestamp - let archive_key = Symbol::new( - env, - &format!("archive_{:?}_{}", market_id, env.ledger().timestamp()), - ); + let archive_key = DataKey::ArchivedMarket(market_id.clone(), env.ledger().timestamp()); Self::set_persistent_with_ttl( env, &archive_key, diff --git a/contracts/predictify-hybrid/src/storage_layout_tests.rs b/contracts/predictify-hybrid/src/storage_layout_tests.rs index e81e15a..04e6837 100644 --- a/contracts/predictify-hybrid/src/storage_layout_tests.rs +++ b/contracts/predictify-hybrid/src/storage_layout_tests.rs @@ -233,12 +233,18 @@ fn test_formatted_key_uniqueness() { let compressed_key_1 = format!("compressed_{:?}", market_id_1); let compressed_key_2 = format!("compressed_{:?}", market_id_2); let compressed_ref_key_1 = format!("compressed_ref_{:?}", market_id_1); - let archive_key_1 = format!("archive_{:?}_1234567890", market_id_1); // Verify formatted keys are unique assert_ne!(compressed_key_1, compressed_key_2); assert_ne!(compressed_key_1, compressed_ref_key_1); - assert_ne!(compressed_key_1, archive_key_1); + + // Test ArchivedMarket DataKey uniqueness + let archive_key_1 = crate::storage::DataKey::ArchivedMarket(market_id_1.clone(), 1234567890); + let archive_key_2 = crate::storage::DataKey::ArchivedMarket(market_id_2.clone(), 1234567890); + let archive_key_3 = crate::storage::DataKey::ArchivedMarket(market_id_1.clone(), 9876543210); + + assert_ne!(archive_key_1, archive_key_2); + assert_ne!(archive_key_1, archive_key_3); } // ===== STORAGE KEY NAMESPACE TESTS =====