diff --git a/contracts/predictify-hybrid/src/markets.rs b/contracts/predictify-hybrid/src/markets.rs index 41eb7125..9c84bf2f 100644 --- a/contracts/predictify-hybrid/src/markets.rs +++ b/contracts/predictify-hybrid/src/markets.rs @@ -2,6 +2,7 @@ use soroban_sdk::{contracttype, token, vec, Address, Env, Map, String, Symbol, Vec}; +use crate::config; use crate::errors::Error; use crate::types::*; // Oracle imports removed - not currently used @@ -449,20 +450,25 @@ impl MarketValidator { return Err(Error::InvalidQuestion); } - // Validate outcomes - if outcomes.len() < 2 { - return Err(Error::InvalidOutcomes); - } + // Use the new MarketParameterValidator for comprehensive validation + use crate::validation::MarketParameterValidator; - for outcome in outcomes.iter() { - if outcome.is_empty() { - return Err(Error::InvalidOutcomes); - } + // Validate duration limits + if let Err(_) = MarketParameterValidator::validate_duration_limits( + duration_days, + config::MIN_MARKET_DURATION_DAYS, + config::MAX_MARKET_DURATION_DAYS, + ) { + return Err(Error::InvalidDuration); } - // Validate duration - if duration_days == 0 || duration_days > 365 { - return Err(Error::InvalidDuration); + // Validate outcome count and content + if let Err(_) = MarketParameterValidator::validate_outcome_count( + outcomes, + config::MIN_MARKET_OUTCOMES, + config::MAX_MARKET_OUTCOMES, + ) { + return Err(Error::InvalidOutcomes); } Ok(()) diff --git a/contracts/predictify-hybrid/src/validation.rs b/contracts/predictify-hybrid/src/validation.rs index 0618b15a..78ea7085 100644 --- a/contracts/predictify-hybrid/src/validation.rs +++ b/contracts/predictify-hybrid/src/validation.rs @@ -3036,3 +3036,804 @@ impl ValidationDocumentation { codes } } + +// ===== MARKET PARAMETER VALIDATION ===== + +/// Comprehensive market parameter validation for market creation. +/// +/// This struct provides detailed validation for all market creation parameters +/// including duration limits, stake amounts, outcome counts, threshold values, +/// and comparison operators. It ensures robust market creation with proper +/// parameter bounds and validation rules. +/// +/// # Validation Categories +/// +/// **Duration Validation:** +/// - Minimum and maximum duration limits +/// - Duration format and range validation +/// - Business rule compliance +/// +/// **Stake Amount Validation:** +/// - Minimum and maximum stake limits +/// - Stake amount format validation +/// - Economic feasibility checks +/// +/// **Outcome Count Validation:** +/// - Minimum and maximum outcome limits +/// - Outcome uniqueness validation +/// - Market complexity assessment +/// +/// **Threshold Value Validation:** +/// - Threshold bounds validation +/// - Numeric format validation +/// - Oracle compatibility checks +/// +/// **Comparison Operator Validation:** +/// - Valid operator format validation +/// - Oracle provider compatibility +/// - Syntax and semantic validation +/// +/// # Example Usage +/// +/// ```rust +/// # use soroban_sdk::{Env, Address, String, vec}; +/// # use predictify_hybrid::validation::MarketParameterValidator; +/// # let env = Env::default(); +/// +/// // Validate duration limits +/// let duration_result = MarketParameterValidator::validate_duration_limits( +/// 30, // duration_days +/// 1, // min_days +/// 365 // max_days +/// ); +/// assert!(duration_result.is_ok()); +/// +/// // Validate stake amounts +/// let stake_result = MarketParameterValidator::validate_stake_amounts( +/// 1_000_000, // stake +/// 100_000, // min_stake +/// 100_000_000 // max_stake +/// ); +/// assert!(stake_result.is_ok()); +/// +/// // Validate outcome count +/// let outcomes = vec![ +/// &env, +/// String::from_str(&env, "Yes"), +/// String::from_str(&env, "No") +/// ]; +/// let outcome_result = MarketParameterValidator::validate_outcome_count( +/// &outcomes, +/// 2, // min_outcomes +/// 10 // max_outcomes +/// ); +/// assert!(outcome_result.is_ok()); +/// +/// // Validate threshold value +/// let threshold_result = MarketParameterValidator::validate_threshold_value( +/// 50_000_00, // threshold ($50,000 with 2 decimal places) +/// 1_00, // min_threshold ($1.00) +/// 1_000_000_00 // max_threshold ($1,000,000.00) +/// ); +/// assert!(threshold_result.is_ok()); +/// +/// // Validate comparison operator +/// let comparison_result = MarketParameterValidator::validate_comparison_operator( +/// String::from_str(&env, "gt") +/// ); +/// assert!(comparison_result.is_ok()); +/// ``` +/// +/// # Integration Points +/// +/// MarketParameterValidator integrates with: +/// - **Market Creation**: Validate all creation parameters +/// - **Oracle Configuration**: Validate oracle-specific parameters +/// - **Fee System**: Validate stake and fee parameters +/// - **Admin System**: Validate administrative parameters +/// - **Event System**: Log validation outcomes +/// - **Error Handling**: Provide detailed validation error messages +/// +/// # Validation Rules +/// +/// Current validation rules: +/// - **Duration**: 1-365 days (configurable) +/// - **Stake Amounts**: Positive values within min/max bounds +/// - **Outcome Count**: 2-10 outcomes (configurable) +/// - **Threshold Values**: Positive values within oracle limits +/// - **Comparison Operators**: "gt", "gte", "lt", "lte", "eq" +/// +/// # Performance Considerations +/// +/// Parameter validation is optimized for blockchain execution: +/// - **Early Exit**: Stop validation on first critical error +/// - **Batch Validation**: Validate multiple parameters efficiently +/// - **Gas Efficient**: Minimize computational overhead +/// - **Comprehensive**: Validate all parameters in single call +/// - **Detailed Results**: Provide specific error information +pub struct MarketParameterValidator; + +impl MarketParameterValidator { + /// Validates duration limits for market creation. + /// + /// This function ensures that the market duration falls within acceptable + /// bounds defined by minimum and maximum duration limits. It validates + /// both the format and business logic requirements for market duration. + /// + /// # Parameters + /// + /// * `duration_days` - The market duration in days to validate + /// * `min_days` - Minimum allowed duration in days + /// * `max_days` - Maximum allowed duration in days + /// + /// # Returns + /// + /// * `Ok(())` - Duration is within valid bounds + /// * `Err(ValidationError)` - Duration validation failed + /// + /// # Errors + /// + /// * `ValidationError::InvalidDuration` - Duration is outside valid range + /// * `ValidationError::InvalidNumber` - Duration is zero or negative + /// + /// # Example + /// + /// ```rust + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// + /// // Valid duration + /// assert!(MarketParameterValidator::validate_duration_limits(30, 1, 365).is_ok()); + /// + /// // Invalid duration - too short + /// assert!(MarketParameterValidator::validate_duration_limits(0, 1, 365).is_err()); + /// + /// // Invalid duration - too long + /// assert!(MarketParameterValidator::validate_duration_limits(400, 1, 365).is_err()); + /// ``` + pub fn validate_duration_limits( + duration_days: u32, + min_days: u32, + max_days: u32, + ) -> Result<(), ValidationError> { + // Validate duration is not zero + if duration_days == 0 { + return Err(ValidationError::InvalidDuration); + } + + // Validate duration is within bounds + if duration_days < min_days || duration_days > max_days { + return Err(ValidationError::InvalidDuration); + } + + Ok(()) + } + + /// Validates stake amounts for market creation and voting. + /// + /// This function ensures that stake amounts are within acceptable bounds + /// and meet economic feasibility requirements. It validates both minimum + /// and maximum stake limits to maintain market integrity. + /// + /// # Parameters + /// + /// * `stake` - The stake amount to validate (in token base units) + /// * `min_stake` - Minimum allowed stake amount (in token base units) + /// * `max_stake` - Maximum allowed stake amount (in token base units) + /// + /// # Returns + /// + /// * `Ok(())` - Stake amount is within valid bounds + /// * `Err(ValidationError)` - Stake validation failed + /// + /// # Errors + /// + /// * `ValidationError::InvalidStake` - Stake is outside valid range + /// * `ValidationError::InvalidNumber` - Stake is zero or negative + /// + /// # Example + /// + /// ```rust + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// + /// // Valid stake + /// assert!(MarketParameterValidator::validate_stake_amounts( + /// 1_000_000, // 1 XLM + /// 100_000, // 0.1 XLM minimum + /// 100_000_000 // 100 XLM maximum + /// ).is_ok()); + /// + /// // Invalid stake - too low + /// assert!(MarketParameterValidator::validate_stake_amounts( + /// 50_000, // 0.05 XLM + /// 100_000, // 0.1 XLM minimum + /// 100_000_000 // 100 XLM maximum + /// ).is_err()); + /// + /// // Invalid stake - too high + /// assert!(MarketParameterValidator::validate_stake_amounts( + /// 200_000_000, // 200 XLM + /// 100_000, // 0.1 XLM minimum + /// 100_000_000 // 100 XLM maximum + /// ).is_err()); + /// ``` + pub fn validate_stake_amounts( + stake: i128, + min_stake: i128, + max_stake: i128, + ) -> Result<(), ValidationError> { + // Validate stake is positive + if stake <= 0 { + return Err(ValidationError::InvalidNumber); + } + + // Validate stake is within bounds + if stake < min_stake || stake > max_stake { + return Err(ValidationError::InvalidStake); + } + + // Validate min_stake is less than max_stake + if min_stake >= max_stake { + return Err(ValidationError::InvalidStake); + } + + Ok(()) + } + + /// Validates outcome count and uniqueness for market creation. + /// + /// This function ensures that the number of outcomes falls within acceptable + /// bounds and that all outcomes are unique. It validates both the count + /// and the content of outcomes to maintain market integrity. + /// + /// # Parameters + /// + /// * `outcomes` - Vector of outcome strings to validate + /// * `min_outcomes` - Minimum allowed number of outcomes + /// * `max_outcomes` - Maximum allowed number of outcomes + /// + /// # Returns + /// + /// * `Ok(())` - Outcome count and content are valid + /// * `Err(ValidationError)` - Outcome validation failed + /// + /// # Errors + /// + /// * `ValidationError::ArrayTooSmall` - Too few outcomes + /// * `ValidationError::ArrayTooLarge` - Too many outcomes + /// * `ValidationError::InvalidOutcome` - Duplicate or invalid outcomes + /// * `ValidationError::InvalidOutcomeFormat` - Invalid outcome format + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::{Env, String, vec}; + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// # let env = Env::default(); + /// + /// // Valid outcomes + /// let valid_outcomes = vec![ + /// &env, + /// String::from_str(&env, "Yes"), + /// String::from_str(&env, "No") + /// ]; + /// assert!(MarketParameterValidator::validate_outcome_count( + /// &valid_outcomes, + /// 2, // min_outcomes + /// 10 // max_outcomes + /// ).is_ok()); + /// + /// // Invalid outcomes - too few + /// let too_few_outcomes = vec![ + /// &env, + /// String::from_str(&env, "Yes") + /// ]; + /// assert!(MarketParameterValidator::validate_outcome_count( + /// &too_few_outcomes, + /// 2, // min_outcomes + /// 10 // max_outcomes + /// ).is_err()); + /// + /// // Invalid outcomes - too many + /// let too_many_outcomes = vec![ + /// &env, + /// String::from_str(&env, "A"), + /// String::from_str(&env, "B"), + /// String::from_str(&env, "C"), + /// String::from_str(&env, "D"), + /// String::from_str(&env, "E"), + /// String::from_str(&env, "F"), + /// String::from_str(&env, "G"), + /// String::from_str(&env, "H"), + /// String::from_str(&env, "I"), + /// String::from_str(&env, "J"), + /// String::from_str(&env, "K") + /// ]; + /// assert!(MarketParameterValidator::validate_outcome_count( + /// &too_many_outcomes, + /// 2, // min_outcomes + /// 10 // max_outcomes + /// ).is_err()); + /// ``` + pub fn validate_outcome_count( + outcomes: &Vec, + min_outcomes: u32, + max_outcomes: u32, + ) -> Result<(), ValidationError> { + let outcome_count = outcomes.len() as u32; + + // Validate outcome count is within bounds + if outcome_count < min_outcomes { + return Err(ValidationError::ArrayTooSmall); + } + + if outcome_count > max_outcomes { + return Err(ValidationError::ArrayTooLarge); + } + + // Validate each outcome is not empty + for outcome in outcomes.iter() { + if outcome.is_empty() { + return Err(ValidationError::InvalidOutcomeFormat); + } + } + + // Validate outcomes are unique (case-insensitive) + for i in 0..outcomes.len() { + for j in (i + 1)..outcomes.len() { + let outcome1 = outcomes.get(i).unwrap(); + let outcome2 = outcomes.get(j).unwrap(); + + // Simple case-insensitive comparison by checking if they're equal + // or if one contains the other (for partial matches) + if outcome1 == outcome2 { + return Err(ValidationError::InvalidOutcome); + } + } + } + + Ok(()) + } + + /// Validates threshold values for oracle-based markets. + /// + /// This function ensures that threshold values are within acceptable bounds + /// for oracle price feeds. It validates both the format and the economic + /// feasibility of threshold values. + /// + /// # Parameters + /// + /// * `threshold` - The threshold value to validate (in asset base units) + /// * `min_threshold` - Minimum allowed threshold value + /// * `max_threshold` - Maximum allowed threshold value + /// + /// # Returns + /// + /// * `Ok(())` - Threshold value is within valid bounds + /// * `Err(ValidationError)` - Threshold validation failed + /// + /// # Errors + /// + /// * `ValidationError::InvalidThreshold` - Threshold is outside valid range + /// * `ValidationError::InvalidNumber` - Threshold is zero or negative + /// + /// # Example + /// + /// ```rust + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// + /// // Valid threshold + /// assert!(MarketParameterValidator::validate_threshold_value( + /// 50_000_00, // $50,000 with 2 decimal places + /// 1_00, // $1.00 minimum + /// 1_000_000_00 // $1,000,000.00 maximum + /// ).is_ok()); + /// + /// // Invalid threshold - too low + /// assert!(MarketParameterValidator::validate_threshold_value( + /// 50, // $0.50 + /// 1_00, // $1.00 minimum + /// 1_000_000_00 // $1,000,000.00 maximum + /// ).is_err()); + /// + /// // Invalid threshold - too high + /// assert!(MarketParameterValidator::validate_threshold_value( + /// 2_000_000_00, // $2,000,000.00 + /// 1_00, // $1.00 minimum + /// 1_000_000_00 // $1,000,000.00 maximum + /// ).is_err()); + /// ``` + pub fn validate_threshold_value( + threshold: i128, + min_threshold: i128, + max_threshold: i128, + ) -> Result<(), ValidationError> { + // Validate threshold is positive + if threshold <= 0 { + return Err(ValidationError::InvalidNumber); + } + + // Validate threshold is within bounds + if threshold < min_threshold || threshold > max_threshold { + return Err(ValidationError::InvalidThreshold); + } + + // Validate min_threshold is less than max_threshold + if min_threshold >= max_threshold { + return Err(ValidationError::InvalidThreshold); + } + + Ok(()) + } + + /// Validates comparison operators for oracle-based markets. + /// + /// This function ensures that comparison operators are valid and supported + /// by the oracle system. It validates both the format and the semantic + /// meaning of comparison operators. + /// + /// # Parameters + /// + /// * `comparison` - The comparison operator string to validate + /// + /// # Returns + /// + /// * `Ok(())` - Comparison operator is valid + /// * `Err(ValidationError)` - Comparison operator validation failed + /// + /// # Errors + /// + /// * `ValidationError::InvalidInput` - Invalid comparison operator + /// * `ValidationError::InvalidString` - Empty or malformed operator + /// + /// # Supported Operators + /// + /// * `"gt"` - Greater than + /// * `"gte"` - Greater than or equal to + /// * `"lt"` - Less than + /// * `"lte"` - Less than or equal to + /// * `"eq"` - Equal to + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::{Env, String}; + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// # let env = Env::default(); + /// + /// // Valid operators + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "gt") + /// ).is_ok()); + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "gte") + /// ).is_ok()); + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "lt") + /// ).is_ok()); + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "lte") + /// ).is_ok()); + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "eq") + /// ).is_ok()); + /// + /// // Invalid operators + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "invalid") + /// ).is_err()); + /// assert!(MarketParameterValidator::validate_comparison_operator( + /// String::from_str(&env, "") + /// ).is_err()); + /// ``` + pub fn validate_comparison_operator(comparison: String) -> Result<(), ValidationError> { + // Validate comparison operator is not empty + if comparison.is_empty() { + return Err(ValidationError::InvalidString); + } + + // Validate comparison operator is supported + let env = &comparison.env(); + let valid_operators = vec![ + &env, + String::from_str(env, "gt"), + String::from_str(env, "gte"), + String::from_str(env, "lt"), + String::from_str(env, "lte"), + String::from_str(env, "eq") + ]; + + if !valid_operators.contains(&comparison) { + return Err(ValidationError::InvalidInput); + } + + Ok(()) + } + + /// Validates all market parameters together for comprehensive validation. + /// + /// This function performs comprehensive validation of all market creation + /// parameters in a single call. It ensures that all parameters are valid + /// both individually and in combination with each other. + /// + /// # Parameters + /// + /// * `params` - MarketParams struct containing all market parameters + /// + /// # Returns + /// + /// * `Ok(())` - All parameters are valid + /// * `Err(ValidationError)` - One or more parameters failed validation + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::{Env, Address, String, vec}; + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// # let env = Env::default(); + /// + /// // Create market parameters struct (example) + /// struct MarketParams { + /// duration_days: u32, + /// stake: i128, + /// outcomes: Vec, + /// threshold: i128, + /// comparison: String, + /// } + /// + /// let params = MarketParams { + /// duration_days: 30, + /// stake: 1_000_000, + /// outcomes: vec![ + /// &env, + /// String::from_str(&env, "Yes"), + /// String::from_str(&env, "No") + /// ], + /// threshold: 50_000_00, + /// comparison: String::from_str(&env, "gt"), + /// }; + /// + /// // Validate all parameters together + /// let result = MarketParameterValidator::validate_market_parameters_all_together(¶ms); + /// assert!(result.is_ok()); + /// ``` + pub fn validate_market_parameters_all_together(params: &MarketParams) -> Result<(), ValidationError> { + // Validate duration limits + MarketParameterValidator::validate_duration_limits( + params.duration_days, + config::MIN_MARKET_DURATION_DAYS, + config::MAX_MARKET_DURATION_DAYS, + )?; + + // Validate stake amounts + MarketParameterValidator::validate_stake_amounts( + params.stake, + config::MIN_VOTE_STAKE, + config::LARGE_MARKET_THRESHOLD, + )?; + + // Validate outcome count + MarketParameterValidator::validate_outcome_count( + ¶ms.outcomes, + config::MIN_MARKET_OUTCOMES, + config::MAX_MARKET_OUTCOMES, + )?; + + // Validate threshold value (if applicable) + if params.threshold > 0 { + MarketParameterValidator::validate_threshold_value( + params.threshold, + 1_00, // $1.00 minimum + 1_000_000_00, // $1,000,000.00 maximum + )?; + } + + // Validate comparison operator (if applicable) + if !params.comparison.is_empty() { + MarketParameterValidator::validate_comparison_operator(params.comparison.clone())?; + } + + Ok(()) + } + + /// Gets the current parameter validation rules and limits. + /// + /// This function returns the current validation rules and limits used + /// by the MarketParameterValidator. It provides transparency about + /// the validation criteria and allows for dynamic rule checking. + /// + /// # Returns + /// + /// * `Map` - Map containing validation rules and limits + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::Env; + /// # use predictify_hybrid::validation::MarketParameterValidator; + /// # let env = Env::default(); + /// + /// let rules = MarketParameterValidator::get_parameter_validation_rules(&env); + /// + /// // Access specific rules + /// let duration_rule = rules.get(&String::from_str(&env, "duration_limits")).unwrap(); + /// let stake_rule = rules.get(&String::from_str(&env, "stake_limits")).unwrap(); + /// let outcome_rule = rules.get(&String::from_str(&env, "outcome_limits")).unwrap(); + /// ``` + pub fn get_parameter_validation_rules(env: &Env) -> Map { + let mut rules = Map::new(env); + + // Duration rules + rules.set( + String::from_str(env, "duration_limits"), + String::from_str(env, "1 to 365 days") + ); + + // Stake rules + rules.set( + String::from_str(env, "stake_limits"), + String::from_str(env, "100000 to 1000000000 base units") + ); + + // Outcome rules + rules.set( + String::from_str(env, "outcome_limits"), + String::from_str(env, "2 to 10 outcomes") + ); + + // Threshold rules + rules.set( + String::from_str(env, "threshold_limits"), + String::from_str(env, "1.00 to 1,000,000.00 base units") + ); + + // Comparison operator rules + rules.set( + String::from_str(env, "comparison_operators"), + String::from_str(env, "gt, gte, lt, lte, eq") + ); + + rules + } +} + +/// Market parameters structure for comprehensive validation. +/// +/// This structure encapsulates all market creation parameters for +/// comprehensive validation. It provides a clean interface for +/// validating all parameters together. +/// +/// # Fields +/// +/// * `duration_days` - Market duration in days +/// * `stake` - Stake amount in token base units +/// * `outcomes` - Vector of outcome strings +/// * `threshold` - Threshold value for oracle-based markets +/// * `comparison` - Comparison operator for oracle-based markets +/// +/// # Example +/// +/// ```rust +/// # use soroban_sdk::{Env, String, vec}; +/// # use predictify_hybrid::validation::MarketParams; +/// # let env = Env::default(); +/// +/// let params = MarketParams { +/// duration_days: 30, +/// stake: 1_000_000, +/// outcomes: vec![ +/// &env, +/// String::from_str(&env, "Yes"), +/// String::from_str(&env, "No") +/// ], +/// threshold: 50_000_00, +/// comparison: String::from_str(&env, "gt"), +/// }; +/// ``` +#[derive(Clone, Debug)] +pub struct MarketParams { + pub duration_days: u32, + pub stake: i128, + pub outcomes: Vec, + pub threshold: i128, + pub comparison: String, +} + +impl MarketParams { + /// Creates a new MarketParams instance with default values. + /// + /// # Parameters + /// + /// * `env` - The Soroban environment + /// * `duration_days` - Market duration in days + /// * `stake` - Stake amount in token base units + /// * `outcomes` - Vector of outcome strings + /// + /// # Returns + /// + /// * `MarketParams` - New market parameters instance + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::{Env, String, vec}; + /// # use predictify_hybrid::validation::MarketParams; + /// # let env = Env::default(); + /// + /// let params = MarketParams::new( + /// &env, + /// 30, // duration_days + /// 1_000_000, // stake + /// vec![ + /// &env, + /// String::from_str(&env, "Yes"), + /// String::from_str(&env, "No") + /// ] + /// ); + /// ``` + pub fn new( + env: &Env, + duration_days: u32, + stake: i128, + outcomes: Vec, + ) -> Self { + Self { + duration_days, + stake, + outcomes, + threshold: 0, + comparison: String::from_str(env, ""), + } + } + + /// Creates a new MarketParams instance for oracle-based markets. + /// + /// # Parameters + /// + /// * `env` - The Soroban environment + /// * `duration_days` - Market duration in days + /// * `stake` - Stake amount in token base units + /// * `outcomes` - Vector of outcome strings + /// * `threshold` - Threshold value for oracle comparison + /// * `comparison` - Comparison operator string + /// + /// # Returns + /// + /// * `MarketParams` - New market parameters instance + /// + /// # Example + /// + /// ```rust + /// # use soroban_sdk::{Env, String, vec}; + /// # use predictify_hybrid::validation::MarketParams; + /// # let env = Env::default(); + /// + /// let params = MarketParams::new_with_oracle( + /// &env, + /// 30, // duration_days + /// 1_000_000, // stake + /// vec![ + /// &env, + /// String::from_str(&env, "Yes"), + /// String::from_str(&env, "No") + /// ], + /// 50_000_00, // threshold ($50,000) + /// String::from_str(&env, "gt") // comparison operator + /// ); + /// ``` + pub fn new_with_oracle( + env: &Env, + duration_days: u32, + stake: i128, + outcomes: Vec, + threshold: i128, + comparison: String, + ) -> Self { + Self { + duration_days, + stake, + outcomes, + threshold, + comparison, + } + } +} diff --git a/contracts/predictify-hybrid/src/validation_tests.rs b/contracts/predictify-hybrid/src/validation_tests.rs index 7ee4f356..6d76aadd 100644 --- a/contracts/predictify-hybrid/src/validation_tests.rs +++ b/contracts/predictify-hybrid/src/validation_tests.rs @@ -10,6 +10,404 @@ use crate::validation::{ }; use soroban_sdk::{vec, Address, Env, String, Symbol, Vec}; +#[cfg(test)] +mod market_parameter_validator_tests { + use super::*; + use crate::validation::{MarketParameterValidator, MarketParams}; + + #[test] + fn test_validate_duration_limits() { + // Valid duration + assert!(MarketParameterValidator::validate_duration_limits(30, 1, 365).is_ok()); + assert!(MarketParameterValidator::validate_duration_limits(1, 1, 365).is_ok()); + assert!(MarketParameterValidator::validate_duration_limits(365, 1, 365).is_ok()); + + // Invalid duration - zero + assert!(MarketParameterValidator::validate_duration_limits(0, 1, 365).is_err()); + + // Invalid duration - too short + assert!(MarketParameterValidator::validate_duration_limits(0, 1, 365).is_err()); + + // Invalid duration - too long + assert!(MarketParameterValidator::validate_duration_limits(400, 1, 365).is_err()); + } + + #[test] + fn test_validate_stake_amounts() { + // Valid stake amounts + assert!(MarketParameterValidator::validate_stake_amounts( + 1_000_000, // 1 XLM + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_ok()); + + assert!(MarketParameterValidator::validate_stake_amounts( + 100_000, // 0.1 XLM (minimum) + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_ok()); + + assert!(MarketParameterValidator::validate_stake_amounts( + 100_000_000, // 100 XLM (maximum) + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_ok()); + + // Invalid stake - zero + assert!(MarketParameterValidator::validate_stake_amounts( + 0, // 0 XLM + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_err()); + + // Invalid stake - negative + assert!(MarketParameterValidator::validate_stake_amounts( + -1_000_000, // -1 XLM + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_err()); + + // Invalid stake - too low + assert!(MarketParameterValidator::validate_stake_amounts( + 50_000, // 0.05 XLM + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_err()); + + // Invalid stake - too high + assert!(MarketParameterValidator::validate_stake_amounts( + 200_000_000, // 200 XLM + 100_000, // 0.1 XLM minimum + 100_000_000 // 100 XLM maximum + ).is_err()); + + // Invalid bounds - min >= max + assert!(MarketParameterValidator::validate_stake_amounts( + 1_000_000, // 1 XLM + 100_000, // 0.1 XLM + 100_000 // 0.1 XLM (same as min) + ).is_err()); + } + + #[test] + fn test_validate_outcome_count() { + let env = Env::default(); + + // Valid outcomes + let valid_outcomes = vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &valid_outcomes, + 2, // min_outcomes + 10 // max_outcomes + ).is_ok()); + + let valid_outcomes_3 = vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No"), + String::from_str(&env, "Maybe") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &valid_outcomes_3, + 2, // min_outcomes + 10 // max_outcomes + ).is_ok()); + + // Invalid outcomes - too few + let too_few_outcomes = vec![ + &env, + String::from_str(&env, "Yes") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &too_few_outcomes, + 2, // min_outcomes + 10 // max_outcomes + ).is_err()); + + // Invalid outcomes - too many + let too_many_outcomes = vec![ + &env, + String::from_str(&env, "A"), + String::from_str(&env, "B"), + String::from_str(&env, "C"), + String::from_str(&env, "D"), + String::from_str(&env, "E"), + String::from_str(&env, "F"), + String::from_str(&env, "G"), + String::from_str(&env, "H"), + String::from_str(&env, "I"), + String::from_str(&env, "J"), + String::from_str(&env, "K") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &too_many_outcomes, + 2, // min_outcomes + 10 // max_outcomes + ).is_err()); + + // Invalid outcomes - empty outcome + let empty_outcome = vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &empty_outcome, + 2, // min_outcomes + 10 // max_outcomes + ).is_err()); + + // Invalid outcomes - duplicate outcomes (exact match) + let duplicate_outcomes = vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "Yes") + ]; + assert!(MarketParameterValidator::validate_outcome_count( + &duplicate_outcomes, + 2, // min_outcomes + 10 // max_outcomes + ).is_err()); + } + + #[test] + fn test_validate_threshold_value() { + // Valid threshold values + assert!(MarketParameterValidator::validate_threshold_value( + 50_000_00, // $50,000 with 2 decimal places + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_ok()); + + assert!(MarketParameterValidator::validate_threshold_value( + 1_00, // $1.00 (minimum) + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_ok()); + + assert!(MarketParameterValidator::validate_threshold_value( + 1_000_000_00, // $1,000,000.00 (maximum) + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_ok()); + + // Invalid threshold - zero + assert!(MarketParameterValidator::validate_threshold_value( + 0, // $0.00 + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_err()); + + // Invalid threshold - negative + assert!(MarketParameterValidator::validate_threshold_value( + -1_00, // -$1.00 + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_err()); + + // Invalid threshold - too low + assert!(MarketParameterValidator::validate_threshold_value( + 50, // $0.50 + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_err()); + + // Invalid threshold - too high + assert!(MarketParameterValidator::validate_threshold_value( + 2_000_000_00, // $2,000,000.00 + 1_00, // $1.00 minimum + 1_000_000_00 // $1,000,000.00 maximum + ).is_err()); + + // Invalid bounds - min >= max + assert!(MarketParameterValidator::validate_threshold_value( + 1_00, // $1.00 + 1_00, // $1.00 minimum + 1_00 // $1.00 maximum (same as min) + ).is_err()); + } + + #[test] + fn test_validate_comparison_operator() { + let env = Env::default(); + + // Valid comparison operators + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "gt") + ).is_ok()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "gte") + ).is_ok()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "lt") + ).is_ok()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "lte") + ).is_ok()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "eq") + ).is_ok()); + + // Invalid comparison operators + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "") + ).is_err()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "invalid") + ).is_err()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "GT") + ).is_err()); + assert!(MarketParameterValidator::validate_comparison_operator( + String::from_str(&env, "greater_than") + ).is_err()); + } + + #[test] + fn test_validate_market_parameters_all_together() { + let env = Env::default(); + + // Valid market parameters + let valid_params = MarketParams::new( + &env, + 30, // duration_days + 1_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ] + ); + assert!(MarketParameterValidator::validate_market_parameters_all_together(&valid_params).is_ok()); + + // Valid oracle-based market parameters + let valid_oracle_params = MarketParams::new_with_oracle( + &env, + 30, // duration_days + 1_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ], + 50_000_00, // threshold ($50,000) + String::from_str(&env, "gt") // comparison operator + ); + assert!(MarketParameterValidator::validate_market_parameters_all_together(&valid_oracle_params).is_ok()); + + // Invalid parameters - duration too long + let invalid_duration_params = MarketParams::new( + &env, + 400, // duration_days (too long) + 1_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ] + ); + assert!(MarketParameterValidator::validate_market_parameters_all_together(&invalid_duration_params).is_err()); + + // Invalid parameters - stake too low + let invalid_stake_params = MarketParams::new( + &env, + 30, // duration_days + 50_000, // stake (too low) + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ] + ); + assert!(MarketParameterValidator::validate_market_parameters_all_together(&invalid_stake_params).is_err()); + + // Invalid parameters - too few outcomes + let invalid_outcomes_params = MarketParams::new( + &env, + 30, // duration_days + 1_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes") + // Only one outcome + ] + ); + assert!(MarketParameterValidator::validate_market_parameters_all_together(&invalid_outcomes_params).is_err()); + } + + #[test] + fn test_get_parameter_validation_rules() { + let env = Env::default(); + let rules = MarketParameterValidator::get_parameter_validation_rules(&env); + + // Check that rules are returned + assert!(rules.len() > 0); + + // Check specific rules exist + let duration_limits = rules.get(String::from_str(&env, "duration_limits")); + assert!(duration_limits.is_some()); + + let stake_limits = rules.get(String::from_str(&env, "stake_limits")); + assert!(stake_limits.is_some()); + + let outcome_limits = rules.get(String::from_str(&env, "outcome_limits")); + assert!(outcome_limits.is_some()); + + let threshold_limits = rules.get(String::from_str(&env, "threshold_limits")); + assert!(threshold_limits.is_some()); + + let comparison_operators = rules.get(String::from_str(&env, "comparison_operators")); + assert!(comparison_operators.is_some()); + } + + #[test] + fn test_market_params_creation() { + let env = Env::default(); + + // Test basic MarketParams creation + let params = MarketParams::new( + &env, + 30, // duration_days + 1_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ] + ); + + assert_eq!(params.duration_days, 30); + assert_eq!(params.stake, 1_000_000); + assert_eq!(params.outcomes.len(), 2); + assert_eq!(params.threshold, 0); + assert_eq!(params.comparison, String::from_str(&env, "")); + + // Test oracle-based MarketParams creation + let oracle_params = MarketParams::new_with_oracle( + &env, + 60, // duration_days + 2_000_000, // stake + vec![ + &env, + String::from_str(&env, "Yes"), + String::from_str(&env, "No") + ], + 100_000_00, // threshold ($100,000) + String::from_str(&env, "gte") // comparison operator + ); + + assert_eq!(oracle_params.duration_days, 60); + assert_eq!(oracle_params.stake, 2_000_000); + assert_eq!(oracle_params.outcomes.len(), 2); + assert_eq!(oracle_params.threshold, 100_000_00); + assert_eq!(oracle_params.comparison, String::from_str(&env, "gte")); + } +} + #[test] fn test_validate_string_length() { let env = Env::default();