Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 10 additions & 17 deletions contract/contracts/predifi-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 35 additions & 0 deletions contract/contracts/predifi-contract/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading