From a9853058284b2cb882a304efeabbe33bb9443abd Mon Sep 17 00:00:00 2001 From: ONEONUORA Date: Thu, 26 Mar 2026 15:20:13 +0100 Subject: [PATCH] feat: CATEGORY_OTHER for non-standard pool categories --- .../contracts/predifi-contract/src/lib.rs | 27 ++++++-------- .../contracts/predifi-contract/src/test.rs | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/contract/contracts/predifi-contract/src/lib.rs b/contract/contracts/predifi-contract/src/lib.rs index 1e19139..064ada8 100644 --- a/contract/contracts/predifi-contract/src/lib.rs +++ b/contract/contracts/predifi-contract/src/lib.rs @@ -807,15 +807,11 @@ pub struct PredifiContract; impl PredifiContract { // ====== Pure Helper Functions (side-effect free, verifiable) ====== - /// Validate that a category symbol is in the allowed list. - /// - /// # Arguments - /// * `env` - The contract environment - /// * `category` - The category symbol to validate - /// - /// # Returns - /// `true` if the category is valid, `false` otherwise - fn validate_category(env: &Env, category: &Symbol) -> bool { + /// Validate that a category symbol is in the allowed list, falling back to CATEGORY_OTHER if not. + /// Canonical categories are defined as constants at the top of the file. + /// Any non-matching category is normalized to CATEGORY_OTHER to ensure compatibility + /// with off-chain analytics while allowing specialized categories in metadata if needed. + fn validate_category(env: &Env, category: &Symbol) -> Symbol { let mut allowed = Vec::new(env); allowed.push_back(CATEGORY_SPORTS); allowed.push_back(CATEGORY_FINANCE); @@ -828,11 +824,11 @@ impl PredifiContract { for i in 0..allowed.len() { if let Some(allowed_cat) = allowed.get(i) { if &allowed_cat == category { - return true; + return category.clone(); } } } - false + CATEGORY_OTHER } /// Pure: Check if pool state transition is valid @@ -1439,11 +1435,8 @@ impl PredifiContract { Self::require_not_paused(&env); creator.require_auth(); - // Validate: category must be in the allowed list - assert!( - Self::validate_category(&env, &category), - "category must be one of the allowed categories" - ); + // Validate: category must be in the allowed list, else fallback to CATEGORY_OTHER + let normalized_category = Self::validate_category(&env, &category); // Validate: token must be on the allowed betting whitelist if !Self::is_token_whitelisted(&env, &token) { @@ -1537,7 +1530,7 @@ impl PredifiContract { outcome: 0, token: token.clone(), total_stake: config.initial_liquidity, - category: category.clone(), + category: normalized_category, description: config.description.clone(), metadata_url: config.metadata_url.clone(), options_count, diff --git a/contract/contracts/predifi-contract/src/test.rs b/contract/contracts/predifi-contract/src/test.rs index 0f7e6e7..b961897 100644 --- a/contract/contracts/predifi-contract/src/test.rs +++ b/contract/contracts/predifi-contract/src/test.rs @@ -413,6 +413,41 @@ fn test_multiple_pools_independent() { assert_eq!(w2, 0); } +#[test] +fn test_invalid_category_fallback() { + let env = Env::default(); + env.mock_all_auths(); + + let (_, client, token_address, _, _, _, _, creator) = setup(&env); + + let pool_id = client.create_pool( + &creator, + &100000u64, + &token_address, + &2u32, + &Symbol::new(&env, "InvalidCat"), + &PoolConfig { + description: String::from_str(&env, "Invalid Category Pool"), + metadata_url: String::from_str(&env, "ipfs://test"), + min_stake: 1i128, + max_stake: 0i128, + max_total_stake: 0, + initial_liquidity: 0i128, + required_resolutions: 1u32, + private: false, + whitelist_key: None, + outcome_descriptions: vec![ + &env, + String::from_str(&env, "Outcome 0"), + String::from_str(&env, "Outcome 1"), + ], + }, + ); + + let pool = client.get_pool(&pool_id); + assert_eq!(pool.category, CATEGORY_OTHER); +} + // ── Access control tests ───────────────────────────────────────────────────── #[test]