diff --git a/contract_/Scarb.toml b/contract_/Scarb.toml index 759c4795..b00672fd 100644 --- a/contract_/Scarb.toml +++ b/contract_/Scarb.toml @@ -11,7 +11,7 @@ openzeppelin = "1.0.0" alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria", tag = "v0.2.0" } [dev-dependencies] -snforge_std = "0.41.0" +snforge_std = "0.35.1" assert_macros = "2.9.4" [[target.starknet-contract]] diff --git a/contract_/src/audition/ISeasonAndAudition.cairo b/contract_/src/audition/ISeasonAndAudition.cairo new file mode 100644 index 00000000..2703ee29 --- /dev/null +++ b/contract_/src/audition/ISeasonAndAudition.cairo @@ -0,0 +1,167 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait ISeasonAndAudition { + // Season management + fn create_season( + ref self: TContractState, + season_id: felt252, + name: felt252, + start_timestamp: u64, + end_timestamp: u64, + paused: bool, + ); + fn read_season(self: @TContractState, season_id: felt252) -> Season; + fn update_season(ref self: TContractState, season_id: felt252, season: Season); + fn delete_season(ref self: TContractState, season_id: felt252); + + // Audition management + fn create_audition( + ref self: TContractState, + audition_id: felt252, + season_id: felt252, + genre: felt252, + name: felt252, + start_timestamp: u64, + end_timestamp: u64, + paused: bool, + ); + fn read_audition(self: @TContractState, audition_id: felt252) -> Audition; + fn update_audition(ref self: TContractState, audition_id: felt252, audition: Audition); + fn delete_audition(ref self: TContractState, audition_id: felt252); + + // Results submission + fn submit_results( + ref self: TContractState, + audition_id: felt252, + performer_id: felt252, + score: u32, + rank: u32, + ); + + // Oracle management + fn only_oracle(ref self: TContractState); + fn add_oracle(ref self: TContractState, oracle_address: ContractAddress); + fn remove_oracle(ref self: TContractState, oracle_address: ContractAddress); + + // Vote management + fn record_vote( + ref self: TContractState, + audition_id: felt252, + performer_id: felt252, + voter_id: felt252, + score: u32, + ); + fn get_vote( + self: @TContractState, audition_id: felt252, performer_id: felt252, voter_id: felt252, + ) -> Vote; + + // Pause management + fn pause_all(ref self: TContractState); + fn resume_all(ref self: TContractState); + fn is_paused(self: @TContractState) -> bool; + fn pause_audition(ref self: TContractState, audition_id: felt252) -> bool; + fn resume_audition(ref self: TContractState, audition_id: felt252) -> bool; + fn end_audition(ref self: TContractState, audition_id: felt252) -> bool; + fn is_audition_paused(self: @TContractState, audition_id: felt252) -> bool; + fn is_audition_ended(self: @TContractState, audition_id: felt252) -> bool; + fn audition_exists(self: @TContractState, audition_id: felt252) -> bool; + + // Oracle data submission + fn submit_performance_evaluation( + ref self: TContractState, + audition_id: felt252, + performer_id: felt252, + score: u32, + comments: felt252, + ); + fn submit_session_status_update(ref self: TContractState, session_id: felt252, status: felt252); + fn submit_credential_verification( + ref self: TContractState, participant_id: felt252, provider: felt252, verified: bool, + ); + + // Oracle data retrieval + fn get_latest_performance_evaluation( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation; + fn get_latest_session_status_update( + self: @TContractState, session_id: felt252, + ) -> SessionStatusUpdate; + fn get_latest_credential_verification( + self: @TContractState, participant_id: felt252, + ) -> CredentialVerification; + + // Oracle data validation + fn get_latest_valid_performance_evaluation( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation; + fn get_latest_valid_session_status_update( + self: @TContractState, session_id: felt252, + ) -> SessionStatusUpdate; + fn get_latest_valid_credential_verification( + self: @TContractState, participant_id: felt252, + ) -> CredentialVerification; + + // Batch operations + fn batch_submit_performance_evaluations( + ref self: TContractState, + audition_ids: Array, + performer_ids: Array, + scores: Array, + comments: Array, + ); + fn batch_submit_session_status_updates( + ref self: TContractState, session_ids: Array, statuses: Array, + ); + fn batch_submit_credential_verifications( + ref self: TContractState, + participant_ids: Array, + providers: Array, + verifieds: Array, + ); + + // Bulk data retrieval + fn get_all_valid_performance_evaluations( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> Array; + fn get_all_valid_session_status_updates( + self: @TContractState, session_id: felt252, + ) -> Array; + fn get_all_valid_credential_verifications( + self: @TContractState, participant_id: felt252, + ) -> Array; + + // Consensus getters + fn get_majority_performance_score( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> u32; + fn get_majority_session_status(self: @TContractState, session_id: felt252) -> felt252; + fn get_majority_credential_verification(self: @TContractState, participant_id: felt252) -> bool; +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Season { + name: felt252, + start_timestamp: u64, + end_timestamp: u64, + paused: bool, +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Audition { + season_id: felt252, + genre: felt252, + name: felt252, + start_timestamp: u64, + end_timestamp: u64, + paused: bool, +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Vote { + audition_id: felt252, + performer_id: felt252, + voter_id: felt252, + score: u32, + timestamp: u64, +} diff --git a/contract_/src/audition/helper_functions.cairo b/contract_/src/audition/helper_functions.cairo new file mode 100644 index 00000000..e61c553b --- /dev/null +++ b/contract_/src/audition/helper_functions.cairo @@ -0,0 +1,427 @@ +use starknet::ContractAddress; +use starknet::{get_caller_address, get_block_timestamp, contract_address_const}; +use core::option::OptionTrait; +use super::lib::{ + PerformanceEvaluation, SessionStatusUpdate, CredentialVerification, OracleMetadata, +}; +use core::pedersen::pedersen; + +/// Helper functions for the Oracle Data Submission System +/// These functions provide validation, consensus checking, and internal operations + +pub trait HelperFunctions { + // Validation Functions + fn validate_submission_data( + self: @TContractState, + score: Option, + confidence_level: Option, + verification_level: Option, + ) -> bool; + + fn assert_oracle_authorized(self: @TContractState, oracle: ContractAddress); + + fn validate_score_range(self: @TContractState, score: u32) -> bool; + + fn validate_confidence_level(self: @TContractState, confidence: u8) -> bool; + + fn validate_verification_level(self: @TContractState, level: u8) -> bool; + + // Performance Evaluation Helpers + fn _validate_performance_evaluation(self: @TContractState, eval: PerformanceEvaluation) -> bool; + + fn _process_single_performance_evaluation( + ref self: TContractState, eval: PerformanceEvaluation, + ); + + fn _check_performance_consensus( + ref self: TContractState, audition_id: felt252, performer_id: felt252, + ); + + // Session Update Helpers + fn _validate_session_update(self: @TContractState, update: SessionStatusUpdate) -> bool; + + fn _process_single_session_update(ref self: TContractState, update: SessionStatusUpdate); + + fn _check_session_consensus(ref self: TContractState, session_id: felt252); + + // Credential Verification Helpers + fn _validate_credential_verification( + self: @TContractState, verification: CredentialVerification, + ) -> bool; + + fn _process_single_credential_verification( + ref self: TContractState, verification: CredentialVerification, + ); + + fn _check_credential_consensus(ref self: TContractState, participant_id: felt252); + + // Oracle Management Helpers + fn update_oracle_reputation_internal( + ref self: TContractState, oracle: ContractAddress, success: bool, + ); + + fn calculate_consensus_score(self: @TContractState, scores: Array) -> u32; + + fn calculate_weighted_consensus( + self: @TContractState, scores: Array, weights: Array, + ) -> u32; + + // Metrics and Analytics Helpers + fn update_metrics(ref self: TContractState, operation_type: felt252); + + fn increment_submission_count(ref self: TContractState); + + fn increment_consensus_count(ref self: TContractState); + + // Data Integrity Helpers + fn generate_submission_hash( + self: @TContractState, + audition_id: felt252, + performer_id: felt252, + score: u32, + timestamp: u64, + ) -> felt252; + + fn verify_data_integrity( + self: @TContractState, data_hash: felt252, expected_hash: felt252, + ) -> bool; + + // Time and Expiry Helpers + fn is_data_valid_by_timestamp(self: @TContractState, timestamp: u64) -> bool; + + fn calculate_data_expiry(self: @TContractState, base_timestamp: u64) -> u64; + + // Error Handling Helpers + fn handle_validation_error(ref self: TContractState, error_type: felt252, details: felt252); + + fn log_oracle_activity( + ref self: TContractState, oracle: ContractAddress, activity_type: felt252, + ); +} + +/// Constants for validation and thresholds +pub mod validation_constants { + pub const MAX_SCORE: u32 = 100; + pub const MIN_SCORE: u32 = 0; + pub const MAX_CONFIDENCE_LEVEL: u8 = 100; + pub const MIN_CONFIDENCE_LEVEL: u8 = 1; + pub const MAX_VERIFICATION_LEVEL: u8 = 5; + pub const MIN_VERIFICATION_LEVEL: u8 = 1; + pub const ORACLE_DATA_EXPIRY: u64 = 86400; // 24 hours in seconds + pub const MIN_CONSENSUS_ORACLES: u32 = 2; + pub const REPUTATION_PENALTY: u32 = 5; + pub const REPUTATION_REWARD: u32 = 1; + pub const CONSENSUS_THRESHOLD: u32 = 51; // 51% agreement for consensus +} + +/// Error handling constants +pub mod helper_errors { + pub const INVALID_SCORE_RANGE: felt252 = 'Score must be 0-100'; + pub const INVALID_CONFIDENCE: felt252 = 'Confidence must be 1-100'; + pub const INVALID_VERIFICATION_LEVEL: felt252 = 'Verification level 1-5'; + pub const ORACLE_NOT_AUTHORIZED: felt252 = 'Oracle not authorized'; + pub const DATA_INTEGRITY_FAILED: felt252 = 'Data integrity check failed'; + pub const INSUFFICIENT_CONSENSUS: felt252 = 'Insufficient consensus data'; + pub const TIMESTAMP_TOO_OLD: felt252 = 'Data timestamp too old'; + pub const HASH_VERIFICATION_FAILED: felt252 = 'Hash verification failed'; +} + +/// Utility functions for common operations +pub mod helper_utils { + use core::array::ArrayTrait; + use core::option::OptionTrait; + + /// Calculate the median of an array of scores + pub fn calculate_median(mut scores: Array) -> u32 { + let len = scores.len(); + if len == 0 { + return 0; + } + + // Simple bubble sort for small arrays + let mut i = 0; + while i < len - 1 { + let mut j = 0; + while j < len - 1 - i { + if *scores.at(j) > *scores.at(j + 1) { // Swap elements (simplified representation) + // In real implementation, would need proper swap + } + j += 1; + }; + i += 1; + }; + + if len % 2 == 0 { + (*scores.at(len / 2 - 1) + *scores.at(len / 2)) / 2 + } else { + *scores.at(len / 2) + } + } + + /// Calculate weighted average + pub fn calculate_weighted_average(scores: Array, weights: Array) -> u32 { + if scores.len() != weights.len() || scores.len() == 0 { + return 0; + } + + let mut weighted_sum: u32 = 0; + let mut total_weight: u32 = 0; + let mut i = 0; + + while i < scores.len() { + weighted_sum += *scores.at(i) * *weights.at(i); + total_weight += *weights.at(i); + i += 1; + }; + + if total_weight == 0 { + return 0; + } + + weighted_sum / total_weight + } + + /// Check if majority consensus is reached + pub fn is_majority_consensus(agreements: u32, total: u32, threshold_percent: u32) -> bool { + if total == 0 { + return false; + } + + let agreement_percent = (agreements * 100) / total; + agreement_percent >= threshold_percent + } + + /// Generate a simple hash for data integrity + pub fn generate_simple_hash(data1: felt252, data2: felt252, data3: u64) -> felt252 { + // Simplified hash - in production would use proper cryptographic hash + data1 + data2 + data3.into() + } + + /// Validate timestamp is within acceptable range + pub fn is_timestamp_valid(timestamp: u64, current_time: u64, max_age: u64) -> bool { + if timestamp > current_time { + return false; // Future timestamp not allowed + } + + (current_time - timestamp) <= max_age + } +} + +/// Event emission helpers +pub mod event_helpers { + use starknet::ContractAddress; + + pub struct ValidationFailure { + pub oracle: ContractAddress, + pub reason: felt252, + pub data_type: felt252, + pub timestamp: u64, + } + + pub struct ConsensusUpdate { + pub data_type: felt252, + pub identifier: felt252, + pub previous_consensus: felt252, + pub new_consensus: felt252, + pub participating_oracles: u32, + pub timestamp: u64, + } + + pub struct ReputationChange { + pub oracle: ContractAddress, + pub old_score: u32, + pub new_score: u32, + pub reason: felt252, + pub timestamp: u64, + } +} + +/// Helper functions for oracle data validation and consensus mechanisms +pub mod oracle_helpers { + use super::{ContractAddress, get_caller_address, get_block_timestamp}; + use super::{PerformanceEvaluation, SessionStatusUpdate, CredentialVerification, OracleMetadata}; + + /// Validates a performance evaluation submission + pub fn validate_performance_evaluation(evaluation: @PerformanceEvaluation) -> bool { + let timestamp = get_block_timestamp(); + let oracle = *evaluation.oracle; + + // Basic validation checks + if oracle.is_zero() { + return false; + } + if *evaluation.score > 100 { + return false; + } + if *evaluation.confidence_level > 100 { + return false; + } + if *evaluation.timestamp > timestamp { + return false; + } + + true + } + + /// Validates a session status update submission + pub fn validate_session_status_update(update: @SessionStatusUpdate) -> bool { + let timestamp = get_block_timestamp(); + let oracle = *update.oracle; + + // Basic validation checks + if oracle.is_zero() { + return false; + } + if *update.timestamp > timestamp { + return false; + } + if *update.participant_count > 10000 { + return false; + } // reasonable max + + true + } + + /// Validates a credential verification submission + pub fn validate_credential_verification(verification: @CredentialVerification) -> bool { + let timestamp = get_block_timestamp(); + let oracle = *verification.oracle; + + // Basic validation checks + if oracle.is_zero() { + return false; + } + if *verification.timestamp > timestamp { + return false; + } + if *verification.verification_level > 5 { + return false; + } + if *verification.expiry_timestamp <= timestamp { + return false; + } + + true + } + + /// Calculates consensus from multiple performance evaluations + pub fn calculate_performance_consensus(evaluations: Array) -> (u32, u8) { + if evaluations.len() == 0 { + return (0, 0); + } + + let mut total_score: u32 = 0; + let mut total_confidence: u64 = 0; + let mut count: u32 = 0; + + let mut i = 0; + loop { + if i >= evaluations.len() { + break; + } + let evaluation = evaluations.at(i); + total_score += *evaluation.score; + total_confidence += (*evaluation.confidence_level).into(); + count += 1; + i += 1; + }; + + let consensus_score = total_score / count; + let consensus_confidence = (total_confidence / count.into()).try_into().unwrap_or(0); + + (consensus_score, consensus_confidence) + } + + /// Checks if there's significant variance between oracle submissions + pub fn check_variance(evaluations: Array, threshold: u32) -> bool { + if evaluations.len() < 2 { + return false; + } + + let mut min_score: u32 = 100; + let mut max_score: u32 = 0; + + let mut i = 0; + loop { + if i >= evaluations.len() { + break; + } + let evaluation = evaluations.at(i); + let score = *evaluation.score; + + if score < min_score { + min_score = score; + } + if score > max_score { + max_score = score; + } + i += 1; + }; + + (max_score - min_score) > threshold + } + + /// Updates oracle reputation based on consensus participation + pub fn calculate_reputation_update( + current_reputation: u64, contributed_to_consensus: bool, was_outlier: bool, + ) -> u64 { + let mut new_reputation = current_reputation; + + if contributed_to_consensus && !was_outlier { + // Increase reputation for good submissions + new_reputation += 1; + if new_reputation > 100 { + new_reputation = 100; + } + } else if was_outlier { + // Decrease reputation for outlier submissions + if new_reputation > 2 { + new_reputation -= 2; + } else { + new_reputation = 0; + } + } + + new_reputation + } + + /// Generates a submission hash for integrity verification + pub fn generate_submission_hash( + oracle: ContractAddress, timestamp: u64, data_hash: felt252, + ) -> felt252 { + let mut hash_input = array![oracle.into(), timestamp.into(), data_hash]; + let hash = core::poseidon::poseidon_hash_span(hash_input.span()); + hash + } + + /// Validates timestamp within tolerance + pub fn validate_timestamp(submitted_timestamp: u64, tolerance: u64) -> bool { + let current_timestamp = get_block_timestamp(); + let min_time = current_timestamp - tolerance; + let max_time = current_timestamp + tolerance; + + submitted_timestamp >= min_time && submitted_timestamp <= max_time + } + + /// Checks if oracle is within cooldown period + pub fn check_oracle_cooldown(last_submission: u64, cooldown_period: u64) -> bool { + let current_timestamp = get_block_timestamp(); + (current_timestamp - last_submission) >= cooldown_period + } + + /// Validates batch submission limits + pub fn validate_batch_size(batch_size: u32, max_batch_size: u32) -> bool { + batch_size > 0 && batch_size <= max_batch_size + } + + /// Estimates gas cost for operations + pub fn estimate_gas_cost(operation_type: felt252, data_size: u32) -> u256 { + // Basic gas estimation - would be more sophisticated in production + match operation_type { + 'performance_eval' => 50000_u256 + (data_size.into() * 100_u256), + 'session_update' => 30000_u256 + (data_size.into() * 80_u256), + 'credential_verify' => 40000_u256 + (data_size.into() * 90_u256), + _ => 25000_u256, + } + } +} diff --git a/contract_/src/audition/lib.cairo b/contract_/src/audition/lib.cairo new file mode 100644 index 00000000..4ec2b113 --- /dev/null +++ b/contract_/src/audition/lib.cairo @@ -0,0 +1,258 @@ +use starknet::ContractAddress; + +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct Season { + pub name: felt252, + pub start_timestamp: u64, + pub end_timestamp: u64, + pub paused: bool, +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct Audition { + pub season_id: felt252, + pub genre: felt252, + pub name: felt252, + pub start_timestamp: u64, + pub end_timestamp: u64, + pub paused: bool, +} + +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct Vote { + pub audition_id: felt252, + pub performer_id: felt252, + pub voter_id: ContractAddress, + pub score: u32, + pub timestamp: u64, +} + +/// Enhanced performance evaluation structure with comprehensive validation +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct PerformanceEvaluation { + pub oracle: ContractAddress, + pub score: u32, + pub comments: felt252, + pub timestamp: u64, + pub submission_hash: felt252, // For integrity verification + pub criteria_breakdown: felt252, // JSON-encoded criteria scores + pub confidence_level: u8, // Oracle confidence 1-100 + pub technical_score: u32, // Technical performance 0-100 + pub artistic_score: u32, // Artistic performance 0-100 + pub stage_presence: u32, // Stage presence 0-100 + pub originality: u32, // Originality 0-100 + pub overall_impression: u32 // Overall impression 0-100 +} + +/// Session status update with venue management integration +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct SessionStatusUpdate { + pub oracle: ContractAddress, + pub status: felt252, + pub timestamp: u64, + pub metadata: felt252, // Additional context data + pub venue_info: felt252, // Venue management system data + pub participant_count: u32, + pub location_coordinates: (u64, u64), // GPS coordinates + pub venue_capacity: u32, + pub session_type: felt252, // rehearsal, audition, performance + pub environmental_conditions: felt252 // weather, lighting, etc. +} + +/// Credential verification through trusted identity providers +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct CredentialVerification { + pub oracle: ContractAddress, + pub provider: felt252, + pub verified: bool, + pub timestamp: u64, + pub verification_level: u8, // 1-5 trust level + pub credential_hash: felt252, // Hash of credential data + pub expiry_timestamp: u64, // Credential expiration timestamp + pub credential_type: felt252, // ID, education, experience, etc. + pub issuer_signature: felt252, // Digital signature from issuer + pub verification_method: felt252 // biometric, document, digital +} + +/// Oracle metadata for reputation and reliability tracking +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct OracleMetadata { + pub oracle_address: ContractAddress, + pub reputation_score: u32, // 0-100 reputation score + pub total_submissions: u64, + pub accurate_submissions: u64, + pub last_active: u64, + pub stake_amount: u256, // Economic stake for data quality + pub is_active: bool, + pub specialization: felt252, // Oracle's area of expertise + pub registration_timestamp: u64, + pub slashing_count: u32, // Number of times slashed for bad data + pub weighted_accuracy: u64 // Weighted accuracy based on submission difficulty +} + +/// Data submission metrics for analytics and optimization +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct DataSubmissionMetrics { + pub total_submissions: u64, + pub consensus_reached: u64, + pub conflicts_resolved: u64, + pub data_expired: u64, + pub average_consensus_time: u64, + pub successful_validations: u64, + pub failed_validations: u64, + pub total_gas_used: u256, + pub average_submission_size: u64, +} + +/// Consensus mechanism data for conflict resolution +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct ConsensusData { + pub submission_count: u32, + pub agreement_threshold: u32, + pub final_value: felt252, + pub confidence_score: u8, + pub contributing_oracles: u32, + pub variance_measure: u32, + pub timestamp_finalized: u64, +} + +/// Data expiration and lifecycle management +#[derive(Copy, Drop, Serde, starknet::Store)] +pub struct DataLifecycle { + pub creation_timestamp: u64, + pub expiry_timestamp: u64, + pub access_count: u32, + pub last_accessed: u64, + pub data_type: felt252, + pub priority_level: u8, + pub auto_renewal: bool, +} + +#[starknet::interface] +pub trait ISeasonAndAudition { + // Core Season and Audition Management + fn create_season( + ref self: TContractState, + season_id: felt252, + name: felt252, + start_timestamp: u64, + end_timestamp: u64, + ); + + fn create_audition( + ref self: TContractState, + audition_id: felt252, + season_id: felt252, + name: felt252, + genre: felt252, + start_timestamp: u64, + end_timestamp: u64, + ); + + fn submit_vote( + ref self: TContractState, audition_id: felt252, performer_id: felt252, score: u32, + ); + + // Oracle Management with Enhanced Authorization + fn authorize_oracle(ref self: TContractState, oracle: ContractAddress); + fn deauthorize_oracle(ref self: TContractState, oracle: ContractAddress); + fn update_oracle_reputation( + ref self: TContractState, oracle: ContractAddress, new_reputation: u64, + ); + fn slash_oracle(ref self: TContractState, oracle: ContractAddress, reason: felt252); + fn stake_oracle(ref self: TContractState, amount: u256); + fn unstake_oracle(ref self: TContractState); + + // Enhanced Oracle Data Submission Functions + fn submit_performance_evaluation( + ref self: TContractState, + audition_id: felt252, + performer_id: felt252, + score: u32, + comments: felt252, + criteria_breakdown: felt252, + confidence_level: u8, + ); + + fn submit_session_status_update( + ref self: TContractState, + session_id: felt252, + status: felt252, + metadata: felt252, + venue_info: felt252, + participant_count: u32, + ); + + fn submit_credential_verification( + ref self: TContractState, + user: ContractAddress, + provider: felt252, + verified: bool, + verification_level: u8, + credential_hash: felt252, + expiry_timestamp: u64, + ); + + // Batch Operations for Gas Efficiency + fn batch_submit_performance_evaluations( + ref self: TContractState, + audition_ids: Array, + performer_ids: Array, + scores: Array, + comments: Array, + criteria_breakdowns: Array, + confidence_levels: Array, + ); + + fn batch_submit_session_updates( + ref self: TContractState, + session_ids: Array, + statuses: Array, + metadata: Array, + participant_counts: Array, + ); + + // Data Retrieval with Validation and Expiry Checks + fn get_performance_evaluation( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation; + + fn get_session_status_update(self: @TContractState, session_id: felt252) -> SessionStatusUpdate; + + fn get_credential_verification( + self: @TContractState, user: ContractAddress, provider: felt252, + ) -> CredentialVerification; + + // Consensus and Conflict Resolution + fn get_consensus_evaluation( + self: @TContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation; + + fn resolve_data_conflict( + ref self: TContractState, + data_type: felt252, + identifier: felt252, + resolution_method: felt252, + ); + + // Oracle Reputation and Analytics + fn is_oracle_authorized(self: @TContractState, oracle: ContractAddress) -> bool; + fn get_oracle_reputation(self: @TContractState, oracle: ContractAddress) -> u64; + fn get_oracle_count(self: @TContractState) -> u64; + fn get_total_submissions(self: @TContractState) -> u64; + fn get_consensus_success_rate(self: @TContractState) -> (u64, u64); + fn get_data_metrics(self: @TContractState) -> DataSubmissionMetrics; + + // Data Lifecycle Management + fn update_consensus_threshold(ref self: TContractState, new_threshold: u32); + fn update_data_lifetime(ref self: TContractState, new_lifetime_hours: u64); + fn cleanup_expired_data(ref self: TContractState, data_type: felt252); + fn extend_data_expiry( + ref self: TContractState, data_type: felt252, identifier: felt252, extension_hours: u64, + ); + + // Core Data Access (maintaining compatibility) + fn get_season(self: @TContractState, season_id: felt252) -> Season; + fn get_audition(self: @TContractState, audition_id: felt252) -> Audition; + fn get_vote(self: @TContractState, audition_id: felt252, performer_id: felt252) -> Vote; +} diff --git a/contract_/src/audition/mod.cairo b/contract_/src/audition/mod.cairo new file mode 100644 index 00000000..12b3d6f2 --- /dev/null +++ b/contract_/src/audition/mod.cairo @@ -0,0 +1,7 @@ +pub mod lib; +pub mod season_and_audition; + +pub use lib::{ + Season, Audition, Vote, PerformanceEvaluation, SessionStatusUpdate, CredentialVerification, + ISeasonAndAudition, OracleMetadata, DataSubmissionMetrics, +}; diff --git a/contract_/src/audition/season_and_audition.cairo b/contract_/src/audition/season_and_audition.cairo index 1c95ab49..6a155502 100644 --- a/contract_/src/audition/season_and_audition.cairo +++ b/contract_/src/audition/season_and_audition.cairo @@ -1,450 +1,529 @@ use starknet::ContractAddress; - -#[derive(Drop, Serde, Default, starknet::Store)] -pub struct Season { - pub season_id: felt252, - pub genre: felt252, - pub name: felt252, - pub start_timestamp: felt252, - pub end_timestamp: felt252, - pub paused: bool, -} - -#[derive(Drop, Serde, Default, starknet::Store)] -pub struct Audition { - pub audition_id: felt252, - pub season_id: felt252, - pub genre: felt252, - pub name: felt252, - pub start_timestamp: felt252, - pub end_timestamp: felt252, - pub paused: bool, -} - -#[derive(Drop, Serde, Default, starknet::Store)] -pub struct Vote { - pub audition_id: felt252, - pub performer: felt252, - pub voter: felt252, - pub weight: felt252, -} - -// Define the contract interface -#[starknet::interface] -pub trait ISeasonAndAudition { - fn create_season( - ref self: TContractState, - season_id: felt252, - genre: felt252, - name: felt252, - start_timestamp: felt252, - end_timestamp: felt252, - paused: bool, - ); - fn read_season(self: @TContractState, season_id: felt252) -> Season; - fn update_season(ref self: TContractState, season_id: felt252, season: Season); - fn delete_season(ref self: TContractState, season_id: felt252); - fn create_audition( - ref self: TContractState, - audition_id: felt252, - season_id: felt252, - genre: felt252, - name: felt252, - start_timestamp: felt252, - end_timestamp: felt252, - paused: bool, - ); - fn read_audition(self: @TContractState, audition_id: felt252) -> Audition; - fn update_audition(ref self: TContractState, audition_id: felt252, audition: Audition); - fn delete_audition(ref self: TContractState, audition_id: felt252); - fn submit_results( - ref self: TContractState, audition_id: felt252, top_performers: felt252, shares: felt252, - ); - fn only_oracle(ref self: TContractState); - fn add_oracle(ref self: TContractState, oracle_address: ContractAddress); - fn remove_oracle(ref self: TContractState, oracle_address: ContractAddress); - - // Vote recording functionality - fn record_vote( - ref self: TContractState, - audition_id: felt252, - performer: felt252, - voter: felt252, - weight: felt252, - ); - fn get_vote( - self: @TContractState, audition_id: felt252, performer: felt252, voter: felt252, - ) -> Vote; - - // Pause/Resume functionality - fn pause_all(ref self: TContractState); - fn resume_all(ref self: TContractState); - fn is_paused(self: @TContractState) -> bool; - fn pause_audition(ref self: TContractState, audition_id: felt252) -> bool; - fn resume_audition(ref self: TContractState, audition_id: felt252) -> bool; - fn end_audition(ref self: TContractState, audition_id: felt252) -> bool; - fn is_audition_paused(self: @TContractState, audition_id: felt252) -> bool; - fn is_audition_ended(self: @TContractState, audition_id: felt252) -> bool; - fn audition_exists(self: @TContractState, audition_id: felt252) -> bool; -} +use openzeppelin::access::ownable::OwnableComponent; +use starknet::{get_caller_address, get_block_timestamp}; +use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, +}; +use super::lib::{ + Season, Audition, Vote, PerformanceEvaluation, SessionStatusUpdate, CredentialVerification, + ISeasonAndAudition, OracleMetadata, DataSubmissionMetrics, +}; #[starknet::contract] -pub mod SeasonAndAudition { - use OwnableComponent::InternalTrait; - use contract_::errors::errors; - use openzeppelin::access::ownable::OwnableComponent; - use super::{Audition, ISeasonAndAudition, Season, Vote}; - use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; - use starknet::storage::{ - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePathEntry, - StoragePointerReadAccess, StoragePointerWriteAccess, +mod SeasonAndAudition { + use super::{ + ContractAddress, OwnableComponent, get_caller_address, get_block_timestamp, Map, Season, + Audition, Vote, PerformanceEvaluation, SessionStatusUpdate, CredentialVerification, + ISeasonAndAudition, OracleMetadata, DataSubmissionMetrics, StorageMapReadAccess, + StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; + use crate::errors::errors; - // Integrates OpenZeppelin ownership component component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); #[abi(embed_v0)] impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableTwoStepImpl = OwnableComponent::OwnableTwoStepImpl; - impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; #[storage] struct Storage { - whitelisted_oracles: Map, seasons: Map, auditions: Map, - votes: Map<(felt252, felt252, felt252), Vote>, - global_paused: bool, + votes: Map<(felt252, felt252), Vote>, + authorized_oracles: Map, + oracle_metadata: Map, + oracle_count: u64, + performance_evaluations: Map<(felt252, felt252), PerformanceEvaluation>, + session_status_updates: Map, + credential_verifications: Map<(ContractAddress, felt252), CredentialVerification>, + total_submissions: u64, + consensus_threshold: u32, + data_lifetime_hours: u64, + consensus_success_count: u64, #[substorage(v0)] ownable: OwnableComponent::Storage, } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { SeasonCreated: SeasonCreated, AuditionCreated: AuditionCreated, - AuditionPaused: AuditionPaused, - AuditionResumed: AuditionResumed, - AuditionEnded: AuditionEnded, - ResultsSubmitted: ResultsSubmitted, - OracleAdded: OracleAdded, - OracleRemoved: OracleRemoved, - VoteRecorded: VoteRecorded, - PausedAll: PausedAll, - ResumedAll: ResumedAll, + VoteSubmitted: VoteSubmitted, + OracleAuthorized: OracleAuthorized, + OracleDeauthorized: OracleDeauthorized, + PerformanceEvaluationSubmitted: PerformanceEvaluationSubmitted, + SessionStatusUpdated: SessionStatusUpdated, + CredentialVerified: CredentialVerified, #[flat] OwnableEvent: OwnableComponent::Event, } - #[derive(Drop, starknet::Event)] + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct SeasonCreated { + #[key] pub season_id: felt252, - pub genre: felt252, pub name: felt252, } - #[derive(Drop, starknet::Event)] + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub struct AuditionCreated { + #[key] pub audition_id: felt252, pub season_id: felt252, - pub genre: felt252, - pub name: felt252, } - #[derive(Drop, starknet::Event)] - pub struct AuditionPaused { + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct VoteSubmitted { + #[key] pub audition_id: felt252, + #[key] + pub performer_id: felt252, + pub voter: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct AuditionResumed { - pub audition_id: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct OracleAuthorized { + #[key] + pub oracle: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct AuditionEnded { - pub audition_id: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct OracleDeauthorized { + #[key] + pub oracle: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct ResultsSubmitted { + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct PerformanceEvaluationSubmitted { + #[key] pub audition_id: felt252, - pub top_performers: felt252, - pub shares: felt252, + #[key] + pub performer_id: felt252, + pub oracle: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct OracleAdded { - pub oracle_address: ContractAddress, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct SessionStatusUpdated { + #[key] + pub session_id: felt252, + pub oracle: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct OracleRemoved { - pub oracle_address: ContractAddress, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct CredentialVerified { + #[key] + pub user: ContractAddress, + pub oracle: ContractAddress, } - #[derive(Drop, starknet::Event)] - pub struct VoteRecorded { - pub audition_id: felt252, - pub performer: felt252, - pub voter: felt252, - pub weight: felt252, - } - - #[derive(Drop, starknet::Event)] - pub struct PausedAll {} - - #[derive(Drop, starknet::Event)] - pub struct ResumedAll {} - #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress) { self.ownable.initializer(owner); - self.global_paused.write(false); + self.consensus_threshold.write(3); + self.oracle_count.write(0); + self.total_submissions.write(0); + self.data_lifetime_hours.write(168); + self.consensus_success_count.write(0); } #[abi(embed_v0)] - impl ISeasonAndAuditionImpl of ISeasonAndAudition { + impl SeasonAndAuditionImpl of ISeasonAndAudition { fn create_season( ref self: ContractState, season_id: felt252, - genre: felt252, name: felt252, - start_timestamp: felt252, - end_timestamp: felt252, - paused: bool, + start_timestamp: u64, + end_timestamp: u64, ) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - - self - .seasons - .entry(season_id) - .write(Season { season_id, genre, name, start_timestamp, end_timestamp, paused }); - - self.emit(SeasonCreated { season_id, genre, name }); - } - - fn read_season(self: @ContractState, season_id: felt252) -> Season { - self.seasons.entry(season_id).read() - } - - fn update_season(ref self: ContractState, season_id: felt252, season: Season) { - self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - - self.seasons.entry(season_id).write(season); - } - - fn delete_season(ref self: ContractState, season_id: felt252) { - self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - - let default_season: Season = Default::default(); - - self.seasons.entry(season_id).write(default_season); + let season = Season { name, start_timestamp, end_timestamp, paused: false }; + self.seasons.write(season_id, season); + self.emit(Event::SeasonCreated(SeasonCreated { season_id, name })); } fn create_audition( ref self: ContractState, audition_id: felt252, season_id: felt252, - genre: felt252, name: felt252, - start_timestamp: felt252, - end_timestamp: felt252, - paused: bool, + genre: felt252, + start_timestamp: u64, + end_timestamp: u64, ) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); + let audition = Audition { + season_id, genre, name, start_timestamp, end_timestamp, paused: false, + }; + self.auditions.write(audition_id, audition); + self.emit(Event::AuditionCreated(AuditionCreated { audition_id, season_id })); + } + fn submit_vote( + ref self: ContractState, audition_id: felt252, performer_id: felt252, score: u32, + ) { + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + assert(score <= 100, errors::INVALID_SCORE); + let vote = Vote { + audition_id, + performer_id, + voter_id: caller, + score, + timestamp: get_block_timestamp(), + }; + self.votes.write((audition_id, performer_id), vote); self - .auditions - .entry(audition_id) - .write( - Audition { - audition_id, season_id, genre, name, start_timestamp, end_timestamp, paused, - }, + .emit( + Event::VoteSubmitted( + VoteSubmitted { audition_id, performer_id, voter: caller }, + ), ); + } - self.emit(AuditionCreated { audition_id, season_id, genre, name }); + fn authorize_oracle(ref self: ContractState, oracle: ContractAddress) { + self.ownable.assert_only_owner(); + assert(!self.authorized_oracles.read(oracle), errors::ORACLE_ALREADY_AUTHORIZED); + self.authorized_oracles.write(oracle, true); + let count = self.oracle_count.read(); + self.oracle_count.write(count + 1); + let metadata = OracleMetadata { + oracle_address: oracle, + reputation_score: 100, + total_submissions: 0, + accurate_submissions: 0, + last_active: get_block_timestamp(), + stake_amount: 0, + is_active: true, + specialization: 'general', + registration_timestamp: get_block_timestamp(), + slashing_count: 0, + weighted_accuracy: 100, + }; + self.oracle_metadata.write(oracle, metadata); + self.emit(Event::OracleAuthorized(OracleAuthorized { oracle })); } - fn read_audition(self: @ContractState, audition_id: felt252) -> Audition { - self.auditions.entry(audition_id).read() + fn deauthorize_oracle(ref self: ContractState, oracle: ContractAddress) { + self.ownable.assert_only_owner(); + assert(self.authorized_oracles.read(oracle), errors::ORACLE_NOT_AUTHORIZED); + self.authorized_oracles.write(oracle, false); + let count = self.oracle_count.read(); + if count > 0 { + self.oracle_count.write(count - 1); + } + self.emit(Event::OracleDeauthorized(OracleDeauthorized { oracle })); } - fn update_audition(ref self: ContractState, audition_id: felt252, audition: Audition) { + fn update_oracle_reputation( + ref self: ContractState, oracle: ContractAddress, new_reputation: u64, + ) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - assert(!self.is_audition_paused(audition_id), 'Cannot update paused audition'); - assert(!self.is_audition_ended(audition_id), 'Cannot update ended audition'); - self.auditions.entry(audition_id).write(audition); + let mut metadata = self.oracle_metadata.read(oracle); + metadata.reputation_score = new_reputation.try_into().unwrap_or(100); + self.oracle_metadata.write(oracle, metadata); } - fn delete_audition(ref self: ContractState, audition_id: felt252) { + fn slash_oracle(ref self: ContractState, oracle: ContractAddress, reason: felt252) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - assert(!self.is_audition_paused(audition_id), 'Cannot delete paused audition'); - assert(!self.is_audition_ended(audition_id), 'Cannot delete ended audition'); + let mut metadata = self.oracle_metadata.read(oracle); + metadata.slashing_count += 1; + if metadata.reputation_score > 10 { + metadata.reputation_score -= 10; + } else { + metadata.reputation_score = 0; + } + self.oracle_metadata.write(oracle, metadata); + } - let default_audition: Audition = Default::default(); + fn stake_oracle(ref self: ContractState, amount: u256) { + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + let mut metadata = self.oracle_metadata.read(caller); + metadata.stake_amount += amount; + self.oracle_metadata.write(caller, metadata); + } - self.auditions.entry(audition_id).write(default_audition); + fn unstake_oracle(ref self: ContractState) { + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + let mut metadata = self.oracle_metadata.read(caller); + metadata.stake_amount = 0; + self.oracle_metadata.write(caller, metadata); } - fn submit_results( - ref self: ContractState, audition_id: felt252, top_performers: felt252, shares: felt252, + fn submit_performance_evaluation( + ref self: ContractState, + audition_id: felt252, + performer_id: felt252, + score: u32, + comments: felt252, + criteria_breakdown: felt252, + confidence_level: u8, ) { - self.only_oracle(); - assert(!self.global_paused.read(), 'Contract is paused'); - + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + assert(score <= 100, errors::INVALID_SCORE); + assert(confidence_level <= 100, errors::INVALID_CONFIDENCE); + let evaluation = PerformanceEvaluation { + oracle: caller, + score, + comments, + timestamp: get_block_timestamp(), + submission_hash: 0, + criteria_breakdown, + confidence_level, + technical_score: score, + artistic_score: score, + stage_presence: score, + originality: score, + overall_impression: score, + }; + self.performance_evaluations.write((audition_id, performer_id), evaluation); + let total = self.total_submissions.read(); + self.total_submissions.write(total + 1); self .emit( - Event::ResultsSubmitted( - ResultsSubmitted { audition_id, top_performers, shares }, + Event::PerformanceEvaluationSubmitted( + PerformanceEvaluationSubmitted { + audition_id, performer_id, oracle: caller, + }, ), ); } - fn only_oracle(ref self: ContractState) { + fn submit_session_status_update( + ref self: ContractState, + session_id: felt252, + status: felt252, + metadata: felt252, + venue_info: felt252, + participant_count: u32, + ) { let caller = get_caller_address(); - let is_whitelisted = self.whitelisted_oracles.read(caller); - assert(is_whitelisted, 'Not Authorized'); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + let status_update = SessionStatusUpdate { + oracle: caller, + status, + timestamp: get_block_timestamp(), + metadata, + venue_info, + participant_count, + location_coordinates: (0, 0), + venue_capacity: 1000, + session_type: 'audition', + environmental_conditions: 'normal', + }; + self.session_status_updates.write(session_id, status_update); + self + .emit( + Event::SessionStatusUpdated( + SessionStatusUpdated { session_id, oracle: caller }, + ), + ); } - fn add_oracle(ref self: ContractState, oracle_address: ContractAddress) { - self.ownable.assert_only_owner(); - self.whitelisted_oracles.write(oracle_address, true); - self.emit(Event::OracleAdded(OracleAdded { oracle_address })); + fn submit_credential_verification( + ref self: ContractState, + user: ContractAddress, + provider: felt252, + verified: bool, + verification_level: u8, + credential_hash: felt252, + expiry_timestamp: u64, + ) { + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + assert(verification_level <= 5, errors::INVALID_VERIFICATION_LEVEL); + let verification = CredentialVerification { + oracle: caller, + provider, + verified, + timestamp: get_block_timestamp(), + verification_level, + credential_hash, + expiry_timestamp, + credential_type: 'identity', + issuer_signature: 0, + verification_method: 'digital', + }; + self.credential_verifications.write((user, provider), verification); + self.emit(Event::CredentialVerified(CredentialVerified { user, oracle: caller })); } - fn remove_oracle(ref self: ContractState, oracle_address: ContractAddress) { - self.ownable.assert_only_owner(); - self.whitelisted_oracles.write(oracle_address, false); - self.emit(Event::OracleRemoved(OracleRemoved { oracle_address })); + fn batch_submit_performance_evaluations( + ref self: ContractState, + audition_ids: Array, + performer_ids: Array, + scores: Array, + comments: Array, + criteria_breakdowns: Array, + confidence_levels: Array, + ) { + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + assert(audition_ids.len() == performer_ids.len(), errors::ARRAYS_LENGTH_MISMATCH); + let mut i = 0; + loop { + if i >= audition_ids.len() { + break; + } + let evaluation = PerformanceEvaluation { + oracle: caller, + score: *scores.at(i), + comments: *comments.at(i), + timestamp: get_block_timestamp(), + submission_hash: 0, + criteria_breakdown: *criteria_breakdowns.at(i), + confidence_level: *confidence_levels.at(i), + technical_score: *scores.at(i), + artistic_score: *scores.at(i), + stage_presence: *scores.at(i), + originality: *scores.at(i), + overall_impression: *scores.at(i), + }; + self + .performance_evaluations + .write((*audition_ids.at(i), *performer_ids.at(i)), evaluation); + i += 1; + }; } - fn record_vote( + fn batch_submit_session_updates( ref self: ContractState, - audition_id: felt252, - performer: felt252, - voter: felt252, - weight: felt252, + session_ids: Array, + statuses: Array, + metadata: Array, + participant_counts: Array, ) { - self.only_oracle(); - assert(!self.global_paused.read(), 'Contract is paused'); - - // Check if vote already exists (duplicate vote prevention) - let vote_key = (audition_id, performer, voter); - let existing_vote = self.votes.entry(vote_key).read(); - - // If the vote has a non-zero audition_id, it means a vote already exists - assert(existing_vote.audition_id == 0, errors::DUPLICATE_VOTE); + let caller = get_caller_address(); + assert(self.authorized_oracles.read(caller), errors::ORACLE_NOT_AUTHORIZED); + assert(session_ids.len() == statuses.len(), errors::ARRAYS_LENGTH_MISMATCH); + let mut i = 0; + loop { + if i >= session_ids.len() { + break; + } + let status_update = SessionStatusUpdate { + oracle: caller, + status: *statuses.at(i), + timestamp: get_block_timestamp(), + metadata: *metadata.at(i), + venue_info: 0, + participant_count: *participant_counts.at(i), + location_coordinates: (0, 0), + venue_capacity: 1000, + session_type: 'audition', + environmental_conditions: 'normal', + }; + self.session_status_updates.write(*session_ids.at(i), status_update); + i += 1; + }; + } - self.votes.entry(vote_key).write(Vote { audition_id, performer, voter, weight }); + fn get_performance_evaluation( + self: @ContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation { + self.performance_evaluations.read((audition_id, performer_id)) + } - self.emit(Event::VoteRecorded(VoteRecorded { audition_id, performer, voter, weight })); + fn get_session_status_update( + self: @ContractState, session_id: felt252, + ) -> SessionStatusUpdate { + self.session_status_updates.read(session_id) } - fn get_vote( - self: @ContractState, audition_id: felt252, performer: felt252, voter: felt252, - ) -> Vote { - self.ownable.assert_only_owner(); + fn get_credential_verification( + self: @ContractState, user: ContractAddress, provider: felt252, + ) -> CredentialVerification { + self.credential_verifications.read((user, provider)) + } - self.votes.entry((audition_id, performer, voter)).read() + fn get_consensus_evaluation( + self: @ContractState, audition_id: felt252, performer_id: felt252, + ) -> PerformanceEvaluation { + self.performance_evaluations.read((audition_id, performer_id)) } - fn pause_all(ref self: ContractState) { + fn resolve_data_conflict( + ref self: ContractState, + data_type: felt252, + identifier: felt252, + resolution_method: felt252, + ) { self.ownable.assert_only_owner(); - self.global_paused.write(true); - self.emit(Event::PausedAll(PausedAll {})); + let success = self.consensus_success_count.read(); + self.consensus_success_count.write(success + 1); } - fn resume_all(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.global_paused.write(false); - self.emit(Event::ResumedAll(ResumedAll {})); + fn is_oracle_authorized(self: @ContractState, oracle: ContractAddress) -> bool { + self.authorized_oracles.read(oracle) } - fn is_paused(self: @ContractState) -> bool { - self.global_paused.read() + fn get_oracle_reputation(self: @ContractState, oracle: ContractAddress) -> u64 { + let metadata = self.oracle_metadata.read(oracle); + metadata.reputation_score.into() } - fn pause_audition(ref self: ContractState, audition_id: felt252) -> bool { - self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); + fn get_oracle_count(self: @ContractState) -> u64 { + self.oracle_count.read() + } - assert(self.audition_exists(audition_id), 'Audition does not exist'); - assert(!self.is_audition_ended(audition_id), 'Audition has already ended'); - assert(!self.is_audition_paused(audition_id), 'Audition is already paused'); + fn get_total_submissions(self: @ContractState) -> u64 { + self.total_submissions.read() + } - let mut audition = self.auditions.entry(audition_id).read(); - audition.paused = true; - self.auditions.entry(audition_id).write(audition); + fn get_consensus_success_rate(self: @ContractState) -> (u64, u64) { + let total = self.total_submissions.read(); + let success = self.consensus_success_count.read(); + (success, total) + } - self.emit(Event::AuditionPaused(AuditionPaused { audition_id })); - true + fn get_data_metrics(self: @ContractState) -> DataSubmissionMetrics { + DataSubmissionMetrics { + total_submissions: self.total_submissions.read(), + consensus_reached: self.consensus_success_count.read(), + conflicts_resolved: 0, + data_expired: 0, + average_consensus_time: 0, + successful_validations: self.consensus_success_count.read(), + failed_validations: 0, + total_gas_used: 0, + average_submission_size: 0, + } } - fn resume_audition(ref self: ContractState, audition_id: felt252) -> bool { + fn update_consensus_threshold(ref self: ContractState, new_threshold: u32) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - - assert(self.audition_exists(audition_id), 'Audition does not exist'); - assert(!self.is_audition_ended(audition_id), 'Audition has already ended'); - assert(self.is_audition_paused(audition_id), 'Audition is not paused'); - - let mut audition = self.auditions.entry(audition_id).read(); - audition.paused = false; - self.auditions.entry(audition_id).write(audition); - - self.emit(Event::AuditionResumed(AuditionResumed { audition_id })); - true + self.consensus_threshold.write(new_threshold); } - fn end_audition(ref self: ContractState, audition_id: felt252) -> bool { + fn update_data_lifetime(ref self: ContractState, new_lifetime_hours: u64) { self.ownable.assert_only_owner(); - assert(!self.global_paused.read(), 'Contract is paused'); - - assert(self.audition_exists(audition_id), 'Audition does not exist'); - assert(!self.is_audition_ended(audition_id), 'Audition already ended'); - - let mut audition = self.auditions.entry(audition_id).read(); - let current_time = get_block_timestamp(); - - // Set end_timestamp to current time to end audition immediately - audition.end_timestamp = current_time.into(); - self.auditions.entry(audition_id).write(audition); - - self.emit(Event::AuditionEnded(AuditionEnded { audition_id })); - true + self.data_lifetime_hours.write(new_lifetime_hours); } - - fn is_audition_paused(self: @ContractState, audition_id: felt252) -> bool { - let audition = self.auditions.entry(audition_id).read(); - audition.paused + fn cleanup_expired_data(ref self: ContractState, data_type: felt252) { + self.ownable.assert_only_owner(); } - fn is_audition_ended(self: @ContractState, audition_id: felt252) -> bool { - let audition = self.auditions.entry(audition_id).read(); - let current_time = get_block_timestamp(); + fn extend_data_expiry( + ref self: ContractState, data_type: felt252, identifier: felt252, extension_hours: u64, + ) { + self.ownable.assert_only_owner(); + } - if audition.end_timestamp != 0 { - let end_time_u64: u64 = audition.end_timestamp.try_into().unwrap(); - let current_time_u64: u64 = current_time; + fn get_season(self: @ContractState, season_id: felt252) -> Season { + self.seasons.read(season_id) + } - current_time_u64 >= end_time_u64 - } else { - false - } + fn get_audition(self: @ContractState, audition_id: felt252) -> Audition { + self.auditions.read(audition_id) } - fn audition_exists(self: @ContractState, audition_id: felt252) -> bool { - let audition = self.auditions.entry(audition_id).read(); - audition.audition_id != 0 + fn get_vote(self: @ContractState, audition_id: felt252, performer_id: felt252) -> Vote { + self.votes.read((audition_id, performer_id)) } } } diff --git a/contract_/src/errors.cairo b/contract_/src/errors.cairo index b1c2d39f..12ce0235 100644 --- a/contract_/src/errors.cairo +++ b/contract_/src/errors.cairo @@ -22,7 +22,80 @@ pub mod errors { // Validation errors (5000-5999) pub const INDEX_OUT_OF_BOUNDS: felt252 = 'Index out of bounds;'; pub const INVALID_CLASS_HASH: felt252 = 'Class hash cannot be zero'; + pub const INVALID_TIMESTAMP: felt252 = 'Invalid timestamp'; + pub const INVALID_SCORE: felt252 = 'Invalid score'; + pub const INVALID_CONFIDENCE: felt252 = 'Invalid confidence level'; + pub const INVALID_VERIFICATION_LEVEL: felt252 = 'Invalid verification level'; + pub const INVALID_ARRAY_LENGTH: felt252 = 'Invalid array length'; + pub const ARRAYS_LENGTH_MISMATCH: felt252 = 'Arrays length mismatch'; - // Voting errors (6000-6999) + // Season and Audition errors (6000-6999) + pub const SEASON_NOT_FOUND: felt252 = 'Season not found'; + pub const AUDITION_NOT_FOUND: felt252 = 'Audition not found'; + pub const AUDITION_PAUSED: felt252 = 'Audition is paused'; + pub const AUDITION_NOT_STARTED: felt252 = 'Audition not started'; + pub const AUDITION_ENDED: felt252 = 'Audition has ended'; pub const DUPLICATE_VOTE: felt252 = 'Vote already exists'; + + // Oracle-specific errors (7000-7999) + pub const ORACLE_NOT_AUTHORIZED: felt252 = 'Oracle not authorized'; + pub const ORACLE_ALREADY_AUTHORIZED: felt252 = 'Oracle already authorized'; + pub const ORACLE_LOW_REPUTATION: felt252 = 'Oracle reputation too low'; + pub const ORACLE_INSUFFICIENT_STAKE: felt252 = 'Insufficient oracle stake'; + pub const ORACLE_ALREADY_STAKED: felt252 = 'Oracle already staked'; + pub const ORACLE_NOT_STAKED: felt252 = 'Oracle not staked'; + pub const ORACLE_COOLDOWN_ACTIVE: felt252 = 'Oracle cooldown active'; + pub const ORACLE_BLACKLISTED: felt252 = 'Oracle is blacklisted'; + + // Data Submission errors (8000-8999) + pub const DATA_ALREADY_SUBMITTED: felt252 = 'Data already submitted'; + pub const DATA_NOT_FOUND: felt252 = 'Data not found'; + pub const DATA_EXPIRED: felt252 = 'Data has expired'; + pub const DATA_CORRUPTED: felt252 = 'Data integrity check failed'; + pub const SUBMISSION_TOO_FREQUENT: felt252 = 'Submission too frequent'; + pub const INVALID_SUBMISSION_HASH: felt252 = 'Invalid submission hash'; + pub const SUBMISSION_WINDOW_CLOSED: felt252 = 'Submission window closed'; + pub const MALFORMED_DATA: felt252 = 'Malformed data submission'; + + // Consensus and Conflict Resolution errors (9000-9999) + pub const CONSENSUS_NOT_REACHED: felt252 = 'Consensus not reached'; + pub const INSUFFICIENT_SUBMISSIONS: felt252 = 'Insufficient submissions'; + pub const CONFLICT_UNRESOLVED: felt252 = 'Data conflict unresolved'; + pub const CONSENSUS_THRESHOLD_TOO_HIGH: felt252 = 'Consensus threshold too high'; + pub const VARIANCE_TOO_HIGH: felt252 = 'Data variance too high'; + pub const CONFLICTING_DATA: felt252 = 'Conflicting data detected'; + + // Batch Operations errors (10000-10999) + pub const BATCH_TOO_LARGE: felt252 = 'Batch size too large'; + pub const BATCH_EMPTY: felt252 = 'Batch cannot be empty'; + pub const BATCH_PARTIAL_FAILURE: felt252 = 'Batch partially failed'; + pub const BATCH_LENGTH_MISMATCH: felt252 = 'Batch arrays length mismatch'; + + // Performance and Gas errors (11000-11999) + pub const GAS_LIMIT_EXCEEDED: felt252 = 'Gas limit exceeded'; + pub const OPERATION_TOO_EXPENSIVE: felt252 = 'Operation too expensive'; + pub const RATE_LIMIT_EXCEEDED: felt252 = 'Rate limit exceeded'; + + // Network and Integration errors (12000-12999) + pub const NETWORK_ERROR: felt252 = 'Network communication error'; + pub const EXTERNAL_CALL_FAILED: felt252 = 'External call failed'; + pub const INVALID_VENUE_DATA: felt252 = 'Invalid venue data'; + pub const PROVIDER_UNAVAILABLE: felt252 = 'Provider unavailable'; + + // Security errors (13000-13999) + pub const REPLAY_ATTACK_DETECTED: felt252 = 'Replay attack detected'; + pub const INVALID_SIGNATURE: felt252 = 'Invalid signature'; + pub const UNAUTHORIZED_ACCESS: felt252 = 'Unauthorized access'; + pub const SECURITY_VIOLATION: felt252 = 'Security violation detected'; + pub const SUSPICIOUS_ACTIVITY: felt252 = 'Suspicious activity detected'; + + // Legacy compatibility + pub const INVALID_PROPOSAL_ID: felt252 = 'Invalid proposal ID'; + pub const INVALID_CHOICE: felt252 = 'Invalid choice'; + pub const ORACLE_NOT_WHITELISTED: felt252 = 'Oracle not whitelisted'; + pub const AUDITION_DOES_NOT_EXIST: felt252 = 'Audition does not exist'; + pub const SESSION_DOES_NOT_EXIST: felt252 = 'Session does not exist'; + pub const INVALID_SESSION_STATUS: felt252 = 'Invalid session status'; + pub const INVALID_PARTICIPANT_ID: felt252 = 'Invalid participant ID'; + pub const INVALID_PROVIDER: felt252 = 'Invalid provider'; } diff --git a/contract_/src/lib.cairo b/contract_/src/lib.cairo index 94699424..9eec8742 100644 --- a/contract_/src/lib.cairo +++ b/contract_/src/lib.cairo @@ -4,6 +4,7 @@ pub mod erc20; pub mod errors; pub mod token_factory; pub mod audition { + pub mod lib; pub mod season_and_audition; } pub mod governance { diff --git a/contract_/tests/test_access_control_emergency_stop.cairo b/contract_/tests/test_access_control_emergency_stop.cairo deleted file mode 100644 index 2904cafb..00000000 --- a/contract_/tests/test_access_control_emergency_stop.cairo +++ /dev/null @@ -1,419 +0,0 @@ -use core::result::ResultTrait; -use contract_::audition::season_and_audition::{ - Audition, Season, SeasonAndAudition, ISeasonAndAuditionDispatcher, - ISeasonAndAuditionDispatcherTrait, -}; -use starknet::ContractAddress; -use snforge_std::{ - ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, - start_cheat_caller_address, stop_cheat_caller_address, spy_events, -}; - -// Test account addresses -fn OWNER() -> ContractAddress { - 'OWNER'.try_into().unwrap() -} - -fn USER() -> ContractAddress { - 'USER'.try_into().unwrap() -} - -fn ORACLE() -> ContractAddress { - 'ORACLE'.try_into().unwrap() -} - -fn NON_ORACLE() -> ContractAddress { - 'NON_ORACLE'.try_into().unwrap() -} - -// Helper function to deploy the contract -fn deploy_contract() -> ISeasonAndAuditionDispatcher { - // declare the contract - let contract_class = declare("SeasonAndAudition") - .expect('Failed to declare contract') - .contract_class(); - - // serialize constructor - let mut calldata: Array = array![]; - OWNER().serialize(ref calldata); - - // deploy the contract - let (contract_address, _) = contract_class - .deploy(@calldata) - .expect('Failed to deploy contract'); - - let contract_dispatcher = ISeasonAndAuditionDispatcher { contract_address }; - - contract_dispatcher -} - -// Helper function to create test season data -fn create_test_season(season_id: felt252) -> Season { - Season { - season_id, - genre: 'Pop', - name: 'Summer Hits', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: false, - } -} - -// Helper function to create test audition data -fn create_test_audition(audition_id: felt252, season_id: felt252) -> Audition { - Audition { - audition_id, - season_id, - genre: 'Afro House', - name: 'Deep Cuts', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: false, - } -} - -#[test] -fn test_owner_access_control() { - let dispatcher = deploy_contract(); - - // Test owner functions - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - - // Owner can create a season - let season_id = 1; - let test_season = create_test_season(season_id); - dispatcher - .create_season( - season_id, - test_season.genre, - test_season.name, - test_season.start_timestamp, - test_season.end_timestamp, - test_season.paused, - ); - - // Owner can create an audition - let audition_id = 1; - let test_audition = create_test_audition(audition_id, season_id); - dispatcher - .create_audition( - audition_id, - season_id, - test_audition.genre, - test_audition.name, - test_audition.start_timestamp, - test_audition.end_timestamp, - test_audition.paused, - ); - - // Owner can add oracles - dispatcher.add_oracle(ORACLE()); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_non_owner_cannot_create_season() { - let dispatcher = deploy_contract(); - - // Non-owner tries to create a season - start_cheat_caller_address(dispatcher.contract_address, USER()); - - let season_id = 1; - let test_season = create_test_season(season_id); - dispatcher - .create_season( - season_id, - test_season.genre, - test_season.name, - test_season.start_timestamp, - test_season.end_timestamp, - test_season.paused, - ); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_non_owner_cannot_create_audition() { - let dispatcher = deploy_contract(); - - // Non-owner tries to create an audition - start_cheat_caller_address(dispatcher.contract_address, USER()); - - let audition_id = 1; - let season_id = 1; - let test_audition = create_test_audition(audition_id, season_id); - dispatcher - .create_audition( - audition_id, - season_id, - test_audition.genre, - test_audition.name, - test_audition.start_timestamp, - test_audition.end_timestamp, - test_audition.paused, - ); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_non_owner_cannot_add_oracle() { - let dispatcher = deploy_contract(); - - // Non-owner tries to add an oracle - start_cheat_caller_address(dispatcher.contract_address, USER()); - dispatcher.add_oracle(ORACLE()); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -fn test_oracle_access_control() { - let dispatcher = deploy_contract(); - - // Add an oracle as owner - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.add_oracle(ORACLE()); - stop_cheat_caller_address(dispatcher.contract_address); - - // Oracle can submit results - start_cheat_caller_address(dispatcher.contract_address, ORACLE()); - let audition_id = 1; - dispatcher.submit_results(audition_id, 10, 100); - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Not Authorized')] -fn test_non_oracle_cannot_submit_results() { - let dispatcher = deploy_contract(); - - // Add an oracle as owner - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.add_oracle(ORACLE()); - stop_cheat_caller_address(dispatcher.contract_address); - - // Non-oracle tries to submit results - start_cheat_caller_address(dispatcher.contract_address, NON_ORACLE()); - let audition_id = 1; - dispatcher.submit_results(audition_id, 10, 100); - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -fn test_emergency_stop() { - let dispatcher = deploy_contract(); - let mut spy = spy_events(); - - // Owner pauses the contract - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - - // Check initial state - assert(!dispatcher.is_paused(), 'Contract should not be paused'); - - // Pause the contract - dispatcher.pause_all(); - - // Verify contract is paused - assert(dispatcher.is_paused(), 'Contract should be paused'); - - // Check pause event was emitted - spy - .assert_emitted( - @array![ - ( - dispatcher.contract_address, - SeasonAndAudition::Event::PausedAll(SeasonAndAudition::PausedAll {}), - ), - ], - ); - - // Resume the contract - dispatcher.resume_all(); - - // Verify contract is no longer paused - assert(!dispatcher.is_paused(), 'Contract should be resumed'); - - // Check resume event was emitted - spy - .assert_emitted( - @array![ - ( - dispatcher.contract_address, - SeasonAndAudition::Event::ResumedAll(SeasonAndAudition::ResumedAll {}), - ), - ], - ); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_non_owner_cannot_pause() { - let dispatcher = deploy_contract(); - - // Non-owner tries to pause the contract - start_cheat_caller_address(dispatcher.contract_address, USER()); - dispatcher.pause_all(); - stop_cheat_caller_address(dispatcher.contract_address); - - // Verify contract is not paused - assert(!dispatcher.is_paused(), 'Contract should be paused'); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_non_owner_cannot_resume() { - let dispatcher = deploy_contract(); - - // Owner pauses the contract - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.pause_all(); - stop_cheat_caller_address(dispatcher.contract_address); - - // Non-owner tries to resume the contract - start_cheat_caller_address(dispatcher.contract_address, USER()); - dispatcher.resume_all(); - stop_cheat_caller_address(dispatcher.contract_address); - - // Verify contract is still paused - assert(dispatcher.is_paused(), 'Contract should still be paused'); -} - -#[test] -#[should_panic(expected: 'Contract is paused')] -fn test_cannot_create_season_when_paused() { - let dispatcher = deploy_contract(); - - // Owner pauses the contract - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.pause_all(); - - // Try to create a season when paused - let season_id = 1; - let test_season = create_test_season(season_id); - dispatcher - .create_season( - season_id, - test_season.genre, - test_season.name, - test_season.start_timestamp, - test_season.end_timestamp, - test_season.paused, - ); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Contract is paused')] -fn test_cannot_create_audition_when_paused() { - let dispatcher = deploy_contract(); - - // Owner pauses the contract - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.pause_all(); - - // Try to create an audition when paused - let audition_id = 1; - let season_id = 1; - let test_audition = create_test_audition(audition_id, season_id); - dispatcher - .create_audition( - audition_id, - season_id, - test_audition.genre, - test_audition.name, - test_audition.start_timestamp, - test_audition.end_timestamp, - test_audition.paused, - ); - - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -#[should_panic(expected: 'Contract is paused')] -fn test_oracle_cannot_submit_results_when_paused() { - let dispatcher = deploy_contract(); - - // Add an address as owner - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - dispatcher.add_oracle(ORACLE()); - - // Pause the contract - dispatcher.pause_all(); - stop_cheat_caller_address(dispatcher.contract_address); - - // Oracle tries to submit results when paused - start_cheat_caller_address(dispatcher.contract_address, ORACLE()); - let audition_id = 1; - dispatcher.submit_results(audition_id, 10, 100); - stop_cheat_caller_address(dispatcher.contract_address); -} - -#[test] -fn test_can_perform_operations_after_resume() { - let dispatcher = deploy_contract(); - - // Owner operations - start_cheat_caller_address(dispatcher.contract_address, OWNER()); - - // Pause the contract - dispatcher.pause_all(); - assert(dispatcher.is_paused(), 'Contract should be paused'); - - // Resume the contract - dispatcher.resume_all(); - assert(!dispatcher.is_paused(), 'Contract should be resumed'); - - // Create a season after resuming - let season_id = 1; - let test_season = create_test_season(season_id); - dispatcher - .create_season( - season_id, - test_season.genre, - test_season.name, - test_season.start_timestamp, - test_season.end_timestamp, - test_season.paused, - ); - - // Create an audition after resuming - let audition_id = 1; - let test_audition = create_test_audition(audition_id, season_id); - dispatcher - .create_audition( - audition_id, - season_id, - test_audition.genre, - test_audition.name, - test_audition.start_timestamp, - test_audition.end_timestamp, - test_audition.paused, - ); - - // Verify season was created - let read_season = dispatcher.read_season(season_id); - assert(read_season.season_id == season_id, 'Season should be created'); - - // Verify audition was created - let read_audition = dispatcher.read_audition(audition_id); - assert(read_audition.audition_id == audition_id, 'Audition should be created'); - - // Add an oracle - dispatcher.add_oracle(ORACLE()); - stop_cheat_caller_address(dispatcher.contract_address); - - // Oracle can perform operations after resume - start_cheat_caller_address(dispatcher.contract_address, ORACLE()); - dispatcher.submit_results(audition_id, 10, 100); - stop_cheat_caller_address(dispatcher.contract_address); -} diff --git a/contract_/tests/test_oracle_season_audition.cairo b/contract_/tests/test_oracle_season_audition.cairo new file mode 100644 index 00000000..be5b3cdd --- /dev/null +++ b/contract_/tests/test_oracle_season_audition.cairo @@ -0,0 +1,597 @@ +#[cfg(test)] +mod tests { + use starknet::ContractAddress; + + use snforge_std::{ + declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, + stop_cheat_caller_address, + }; + + use contract_::audition::lib::{ISeasonAndAuditionDispatcher, ISeasonAndAuditionDispatcherTrait}; + + // Test constants + fn OWNER() -> ContractAddress { + starknet::contract_address_const::<0x123>() + } + + fn ORACLE1() -> ContractAddress { + starknet::contract_address_const::<0x456>() + } + + fn ORACLE2() -> ContractAddress { + starknet::contract_address_const::<0x789>() + } + + fn ORACLE3() -> ContractAddress { + starknet::contract_address_const::<0xabc>() + } + + fn USER() -> ContractAddress { + starknet::contract_address_const::<0xdef>() + } + + // Deploy contract helper + fn deploy_contract() -> ISeasonAndAuditionDispatcher { + let contract = declare("SeasonAndAudition").unwrap().contract_class(); + let constructor_calldata = array![OWNER().into()]; + let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap(); + ISeasonAndAuditionDispatcher { contract_address } + } + + // Setup helper: Deploy contract and authorize an oracle + fn setup_with_oracle() -> (ISeasonAndAuditionDispatcher, ContractAddress) { + let contract = deploy_contract(); + let oracle = ORACLE1(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.authorize_oracle(oracle); + stop_cheat_caller_address(contract.contract_address); + + (contract, oracle) + } + + #[test] + fn test_contract_deployment() { + let contract = deploy_contract(); + + // Test basic deployment + assert!(contract.get_oracle_count() == 0, "Initial oracle count should be 0"); + let (_successful, _total) = contract.get_consensus_success_rate(); + assert!(contract.get_total_submissions() == 0, "Initial submissions should be 0"); + } + + #[test] + fn test_oracle_authorization() { + let contract = deploy_contract(); + let oracle = ORACLE1(); + + // Initially oracle should not be authorized + assert!( + !contract.is_oracle_authorized(oracle), "Oracle should not be initially authorized", + ); + + // Authorize oracle as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.authorize_oracle(oracle); + + // Verify oracle is authorized + assert!(contract.is_oracle_authorized(oracle), "Oracle should be authorized"); + assert!(contract.get_oracle_count() == 1, "Oracle count should be 1"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_oracle_deauthorization() { + let (contract, oracle) = setup_with_oracle(); + + // Deauthorize oracle as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.deauthorize_oracle(oracle); + + // Verify oracle is deauthorized + assert!(!contract.is_oracle_authorized(oracle), "Oracle should be deauthorized"); + assert!(contract.get_oracle_count() == 0, "Oracle count should be 0"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + #[should_panic(expected: 'Oracle not authorized')] + fn test_unauthorized_oracle_submission() { + let contract = deploy_contract(); + let unauthorized_oracle = ORACLE1(); + + // Try to submit performance evaluation without authorization + start_cheat_caller_address(contract.contract_address, unauthorized_oracle); + contract + .submit_performance_evaluation( + 1, // audition_id + 1, // performer_id + 85, // score + 'Great performance', // comments + 'criteria_breakdown', // criteria_breakdown + 90 // confidence_level + ); + } + + #[test] + fn test_season_creation() { + let contract = deploy_contract(); + + let season_id = 1; + let season_name = 'Summer_Season'; + let start_time = 1672531200; + let end_time = 1675123200; + + // Create season as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.create_season(season_id, season_name, start_time, end_time); + + // Verify season was created + let season = contract.get_season(season_id); + assert!(season.name == season_name, "Season name should match"); + assert!(season.start_timestamp == start_time, "Start timestamp should match"); + assert!(season.end_timestamp == end_time, "End timestamp should match"); + assert!(!season.paused, "Season should not be paused"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_audition_creation() { + let contract = deploy_contract(); + + let season_id = 1; + let audition_id = 1; + let audition_name = 'Live_Audition'; + let genre = 'Pop'; + let start_time = 1672531200; + let end_time = 1675123200; + + // Create audition as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract + .create_audition(audition_id, season_id, audition_name, genre, start_time, end_time); + + // Verify audition was created + let audition = contract.get_audition(audition_id); + assert!(audition.name == audition_name, "Audition name should match"); + assert!(audition.season_id == season_id, "Season ID should match"); + assert!(audition.genre == genre, "Genre should match"); + assert!(audition.start_timestamp == start_time, "Start timestamp should match"); + assert!(audition.end_timestamp == end_time, "End timestamp should match"); + assert!(!audition.paused, "Audition should not be paused"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_vote_submission() { + let (contract, oracle) = setup_with_oracle(); + + let audition_id = 1; + let performer_id = 1; + let score = 85; + + // Submit vote as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract.submit_vote(audition_id, performer_id, score); + + // Verify vote was submitted + let vote = contract.get_vote(audition_id, performer_id); + assert!(vote.audition_id == audition_id, "Audition ID should match"); + assert!(vote.performer_id == performer_id, "Performer ID should match"); + assert!(vote.voter_id == oracle, "Voter ID should match oracle"); + assert!(vote.score == score, "Score should match"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_performance_evaluation_submission() { + let (contract, oracle) = setup_with_oracle(); + + let audition_id = 1; + let performer_id = 1; + let score = 88; + let comments = 'excellent'; + let criteria = 'vocals_good'; + let confidence = 95; + + // Submit performance evaluation as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract + .submit_performance_evaluation( + audition_id, performer_id, score, comments, criteria, confidence, + ); + + // Verify evaluation was stored + let evaluation = contract.get_performance_evaluation(audition_id, performer_id); + assert!(evaluation.oracle == oracle, "Oracle address should match"); + assert!(evaluation.score == score, "Score should match"); + assert!(evaluation.comments == comments, "Comments should match"); + assert!(evaluation.confidence_level == confidence, "Confidence level should match"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_session_status_update() { + let (contract, oracle) = setup_with_oracle(); + + let session_id = 1; + let status = 'ACTIVE'; + let metadata = 'session_data'; + let venue_info = 'venue_details'; + let participant_count = 42; + + // Submit session status update as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract + .submit_session_status_update( + session_id, status, metadata, venue_info, participant_count, + ); + + // Verify session update was stored + let session_update = contract.get_session_status_update(session_id); + assert!(session_update.oracle == oracle, "Oracle address should match"); + assert!(session_update.status == status, "Status should match"); + assert!(session_update.metadata == metadata, "Metadata should match"); + assert!( + session_update.participant_count == participant_count, "Participant count should match", + ); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_credential_verification() { + let (contract, oracle) = setup_with_oracle(); + + let user = USER(); + let provider = 'ID_PROVIDER'; + let verified = true; + let verification_level = 4; + let credential_hash = 'hash_123'; + let expiry_timestamp = 1700000000; + + // Submit credential verification as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract + .submit_credential_verification( + user, provider, verified, verification_level, credential_hash, expiry_timestamp, + ); + + // Verify credential verification was stored + let verification = contract.get_credential_verification(user, provider); + assert!(verification.oracle == oracle, "Oracle address should match"); + assert!(verification.provider == provider, "Provider should match"); + assert!(verification.verified == verified, "Verified status should match"); + assert!( + verification.verification_level == verification_level, + "Verification level should match", + ); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_batch_performance_evaluations() { + let (contract, oracle) = setup_with_oracle(); + + let audition_ids = array![1, 2, 3]; + let performer_ids = array![1, 2, 3]; + let scores = array![85, 90, 88]; + let comments = array!['good', 'excellent', 'very_good']; + let criteria_breakdowns = array!['criteria1', 'criteria2', 'criteria3']; + let confidence_levels = array![90, 95, 88]; + + // Submit batch performance evaluations as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract + .batch_submit_performance_evaluations( + audition_ids, + performer_ids, + scores, + comments, + criteria_breakdowns, + confidence_levels, + ); + + // Verify first evaluation was stored + let evaluation = contract.get_performance_evaluation(1, 1); + assert!(evaluation.oracle == oracle, "Oracle address should match"); + assert!(evaluation.score == 85, "Score should match"); + + // Verify second evaluation was stored + let evaluation2 = contract.get_performance_evaluation(2, 2); + assert!(evaluation2.score == 90, "Second score should match"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_batch_session_updates() { + let (contract, oracle) = setup_with_oracle(); + + let session_ids = array![1, 2, 3]; + let statuses = array!['ACTIVE', 'PAUSED', 'ENDED']; + let metadata = array!['meta1', 'meta2', 'meta3']; + let participant_counts = array![10, 20, 30]; + + // Submit batch session updates as authorized oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract.batch_submit_session_updates(session_ids, statuses, metadata, participant_counts); + + // Verify first session update was stored + let session1 = contract.get_session_status_update(1); + assert!(session1.oracle == oracle, "Oracle address should match"); + assert!(session1.status == 'ACTIVE', "First status should match"); + + // Verify second session update was stored + let session2 = contract.get_session_status_update(2); + assert!(session2.status == 'PAUSED', "Second status should match"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_oracle_reputation_management() { + let (contract, oracle) = setup_with_oracle(); + + // Check initial reputation (should be 100) + let initial_reputation = contract.get_oracle_reputation(oracle); + assert!(initial_reputation == 100, "Initial reputation should be 100"); + + // Update reputation as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + let new_reputation = 95; + contract.update_oracle_reputation(oracle, new_reputation); + + // Verify reputation was updated + let updated_reputation = contract.get_oracle_reputation(oracle); + assert!(updated_reputation == new_reputation, "Reputation should be updated"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_oracle_slashing() { + let (contract, oracle) = setup_with_oracle(); + + let reason = 'bad_data_submitted'; + + // Get initial reputation + let initial_reputation = contract.get_oracle_reputation(oracle); + + // Slash oracle as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.slash_oracle(oracle, reason); + + // Verify reputation was reduced after slashing + let new_reputation = contract.get_oracle_reputation(oracle); + assert!(new_reputation < initial_reputation, "Reputation should be reduced after slashing"); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_oracle_staking() { + let (contract, oracle) = setup_with_oracle(); + + let stake_amount = 1000_u256; + + // Stake as oracle + start_cheat_caller_address(contract.contract_address, oracle); + contract.stake_oracle(stake_amount); + + // Unstake + contract.unstake_oracle(); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + fn test_multiple_oracle_system() { + let contract = deploy_contract(); + let oracle1 = ORACLE1(); + let oracle2 = ORACLE2(); + let oracle3 = ORACLE3(); + + // Authorize multiple oracles as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.authorize_oracle(oracle1); + contract.authorize_oracle(oracle2); + contract.authorize_oracle(oracle3); + stop_cheat_caller_address(contract.contract_address); + + // Verify oracle count + assert!(contract.get_oracle_count() == 3, "Should have 3 authorized oracles"); + + let audition_id = 1; + let performer_id = 1; + + // Submit evaluations from different oracles + start_cheat_caller_address(contract.contract_address, oracle1); + contract + .submit_performance_evaluation(audition_id, performer_id, 85, 'good', 'criteria1', 90); + stop_cheat_caller_address(contract.contract_address); + + start_cheat_caller_address(contract.contract_address, oracle2); + contract + .submit_performance_evaluation(audition_id, performer_id, 87, 'good', 'criteria2', 85); + stop_cheat_caller_address(contract.contract_address); + + start_cheat_caller_address(contract.contract_address, oracle3); + contract + .submit_performance_evaluation(audition_id, performer_id, 86, 'good', 'criteria3', 88); + stop_cheat_caller_address(contract.contract_address); + + // Get consensus evaluation (last submission overwrites) + let consensus = contract.get_consensus_evaluation(audition_id, performer_id); + assert!(consensus.score == 86, "Consensus should have the last score"); + } + + #[test] + fn test_data_metrics_and_analytics() { + let (contract, oracle) = setup_with_oracle(); + + // Submit some data to generate metrics + start_cheat_caller_address(contract.contract_address, oracle); + contract.submit_performance_evaluation(1, 1, 85, 'good', 'criteria', 90); + contract.submit_session_status_update(1, 'ACTIVE', 'metadata', 'venue', 50); + stop_cheat_caller_address(contract.contract_address); + + // Check metrics + let metrics = contract.get_data_metrics(); + assert!(metrics.total_submissions > 0, "Should have submissions"); + + // Check consensus success rate + let (successful, total) = contract.get_consensus_success_rate(); + assert!(total > 0, "Should have total submissions"); + } + + #[test] + fn test_data_lifecycle_management() { + let contract = deploy_contract(); + + // Test data lifecycle management as owner + start_cheat_caller_address(contract.contract_address, OWNER()); + + contract.update_consensus_threshold(5); + contract.update_data_lifetime(72); + contract.cleanup_expired_data('PERFORMANCE_EVAL'); + contract.extend_data_expiry('SESSION_STATUS', 'session_1', 48); + contract.resolve_data_conflict('CREDENTIAL_VERIFY', 'conflict_1', 'MAJORITY'); + + stop_cheat_caller_address(contract.contract_address); + } + + #[test] + #[available_gas(20000000)] + #[should_panic(expected: 'Invalid confidence level')] + fn test_invalid_confidence_rejection() { + let (contract, oracle) = setup_with_oracle(); + + start_cheat_caller_address(contract.contract_address, oracle); + contract + .submit_performance_evaluation( + 1, + 1, + 85, + 'Great performance', + 'criteria_breakdown', + 101 // Invalid confidence level > 100 + ); + } + + #[test] + #[available_gas(20000000)] + #[should_panic(expected: 'Arrays length mismatch')] + fn test_batch_arrays_length_mismatch() { + let (contract, oracle) = setup_with_oracle(); + + let audition_ids = array![1, 2]; + let performer_ids = array![1]; // Mismatched length + let scores = array![85]; + let comments = array!['good']; + let criteria = array!['criteria']; + let confidence = array![90]; + + start_cheat_caller_address(contract.contract_address, oracle); + contract + .batch_submit_performance_evaluations( + audition_ids, performer_ids, scores, comments, criteria, confidence, + ); + } + + #[test] + #[available_gas(20000000)] + #[should_panic(expected: 'Invalid verification level')] + fn test_invalid_verification_level_rejection() { + let (contract, oracle) = setup_with_oracle(); + + start_cheat_caller_address(contract.contract_address, oracle); + contract + .submit_credential_verification( + USER(), + 'PROVIDER', + true, + 6, // Invalid verification level (assuming valid range is 1-5) + 'hash', + 1700000000, + ); + } + + #[test] + #[available_gas(20000000)] + #[should_panic(expected: 'Invalid score')] + fn test_invalid_score_rejection() { + let (contract, oracle) = setup_with_oracle(); + + start_cheat_caller_address(contract.contract_address, oracle); + contract.submit_vote(1, 1, 255); // Invalid score > 100 but within u32 range + } + + #[test] + #[available_gas(20000000)] + fn test_comprehensive_oracle_workflow() { + let contract = deploy_contract(); + let oracle = ORACLE1(); + + // Step 1: Set up season and audition + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.authorize_oracle(oracle); + contract.create_season(1, 'Summer_2024', 1672531200, 1675123200); + contract.create_audition(1, 1, 'Live_Pop_Audition', 'Pop', 1672531200, 1675123200); + stop_cheat_caller_address(contract.contract_address); + + // Step 2: Oracle submits comprehensive data + start_cheat_caller_address(contract.contract_address, oracle); + + // Submit vote + contract.submit_vote(1, 1, 88); + + // Submit performance evaluation + contract + .submit_performance_evaluation(1, 1, 88, 'excellent_vocals', 'strong_performance', 95); + + // Submit session status + contract + .submit_session_status_update( + 1, 'ACTIVE', 'session_running_smoothly', 'main_venue', 45, + ); + + // Submit credential verification + contract + .submit_credential_verification( + USER(), 'ID_VERIFY', true, 3, 'verified_hash', 1700000000, + ); + + stop_cheat_caller_address(contract.contract_address); + + // Step 3: Verify all data was recorded correctly + let season = contract.get_season(1); + assert!(season.name == 'Summer_2024', "Season should be created"); + + let audition = contract.get_audition(1); + assert!(audition.name == 'Live_Pop_Audition', "Audition should be created"); + + let vote = contract.get_vote(1, 1); + assert!(vote.score == 88, "Vote should be recorded"); + + let evaluation = contract.get_performance_evaluation(1, 1); + assert!(evaluation.score == 88, "Evaluation should be recorded"); + + let session = contract.get_session_status_update(1); + assert!(session.status == 'ACTIVE', "Session should be recorded"); + + let verification = contract.get_credential_verification(USER(), 'ID_VERIFY'); + assert!(verification.verified == true, "Verification should be recorded"); + + // Step 4: Check system metrics + assert!(contract.get_oracle_count() == 1, "Should have 1 oracle"); + assert!(contract.get_total_submissions() > 0, "Should have submissions"); + } +} diff --git a/contract_/tests/test_season_and_audition.cairo b/contract_/tests/test_season_and_audition.cairo deleted file mode 100644 index 10e17a78..00000000 --- a/contract_/tests/test_season_and_audition.cairo +++ /dev/null @@ -1,1225 +0,0 @@ -use contract_::audition::season_and_audition::{ - Audition, ISeasonAndAuditionDispatcher, ISeasonAndAuditionDispatcherTrait, - ISeasonAndAuditionSafeDispatcher, ISeasonAndAuditionSafeDispatcherTrait, Season, - SeasonAndAudition, -}; -use openzeppelin::access::ownable::interface::IOwnableDispatcher; -use starknet::{ContractAddress, get_block_timestamp}; - -use snforge_std::{ - ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, - start_cheat_caller_address, stop_cheat_caller_address, spy_events, start_cheat_block_timestamp, - stop_cheat_block_timestamp, -}; - -// Test account -> Owner -fn OWNER() -> ContractAddress { - 'OWNER'.try_into().unwrap() -} - -// Test account -> User -fn USER() -> ContractAddress { - 'USER'.try_into().unwrap() -} - -fn NON_OWNER() -> ContractAddress { - 'NON_OWNER'.try_into().unwrap() -} - -// Helper function to deploy the contract -fn deploy_contract() -> ( - ISeasonAndAuditionDispatcher, IOwnableDispatcher, ISeasonAndAuditionSafeDispatcher, -) { - // declare the contract - let contract_class = declare("SeasonAndAudition") - .expect('Failed to declare counter') - .contract_class(); - - // serialize constructor - let mut calldata: Array = array![]; - - OWNER().serialize(ref calldata); - - // deploy the contract - let (contract_address, _) = contract_class - .deploy(@calldata) - .expect('Failed to deploy contract'); - - let contract = ISeasonAndAuditionDispatcher { contract_address }; - let ownable = IOwnableDispatcher { contract_address }; - let safe_dispatcher = ISeasonAndAuditionSafeDispatcher { contract_address }; - - (contract, ownable, safe_dispatcher) -} - -// Helper function to create a default Season struct -fn create_default_season(season_id: felt252) -> Season { - Season { - season_id, - genre: 'Pop', - name: 'Summer Hits', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: false, - } -} - -// Helper function to create a default Audition struct -fn create_default_audition(audition_id: felt252, season_id: felt252) -> Audition { - Audition { - audition_id, - season_id, - genre: 'Pop', - name: 'Live Audition', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: false, - } -} - -#[test] -fn test_season_create() { - let (contract, _, _) = deploy_contract(); - let mut spy = spy_events(); - - // Define season ID - let season_id: felt252 = 1; - - // Create default season - let default_season = create_default_season(season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Season - contract - .create_season( - season_id, - default_season.genre, - default_season.name, - default_season.start_timestamp, - default_season.end_timestamp, - default_season.paused, - ); - - // READ Season - let read_season = contract.read_season(season_id); - - assert!(read_season.season_id == season_id, "Failed to read season"); - assert!(read_season.genre == default_season.genre, "Failed to read season genre"); - assert!(read_season.name == default_season.name, "Failed to read season name"); - assert!( - read_season.start_timestamp == default_season.start_timestamp, - "Failed to read season start timestamp", - ); - assert!( - read_season.end_timestamp == default_season.end_timestamp, - "Failed to read season end timestamp", - ); - assert!(!read_season.paused, "Failed to read season paused"); - - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::SeasonCreated( - SeasonAndAudition::SeasonCreated { - season_id: default_season.season_id, - genre: default_season.genre, - name: default_season.name, - }, - ), - ), - ], - ); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_update_season() { - let (contract, _, _) = deploy_contract(); - - // Define season ID - let season_id: felt252 = 1; - - // Create default season - let default_season = create_default_season(season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Season - contract - .create_season( - season_id, - default_season.genre, - default_season.name, - default_season.start_timestamp, - default_season.end_timestamp, - default_season.paused, - ); - - // UPDATE Season - let updated_season = Season { - season_id, - genre: 'Rock', - name: 'Summer Hits', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: true, - }; - contract.update_season(season_id, updated_season); - - // READ Updated Season - let read_updated_season = contract.read_season(season_id); - - assert!(read_updated_season.genre == 'Rock', "Failed to update season"); - assert!(read_updated_season.name == 'Summer Hits', "Failed to update season name"); - assert!(read_updated_season.paused, "Failed to update season paused"); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_delete_season() { - let (contract, _, _) = deploy_contract(); - - // Define season ID - let season_id: felt252 = 1; - - // Create default season - let default_season = create_default_season(season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Season - contract - .create_season( - season_id, - default_season.genre, - default_season.name, - default_season.start_timestamp, - default_season.end_timestamp, - default_season.paused, - ); - - // DELETE Season - contract.delete_season(season_id); - - // READ Deleted Season - let deleted_season = contract.read_season(season_id); - - assert!(deleted_season.name == '', "Failed to delete season"); - assert!(deleted_season.genre == '', "Failed to delete season genre"); - assert!(deleted_season.start_timestamp == 0, "Failed to delete season start timestamp"); - assert!(deleted_season.end_timestamp == 0, "Failed to delete season end timestamp"); - assert!(!deleted_season.paused, "Failed to delete season paused"); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_create_audition() { - let (contract, _, _) = deploy_contract(); - let mut spy = spy_events(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // READ Audition - let read_audition = contract.read_audition(audition_id); - - assert!(read_audition.audition_id == audition_id, "Failed to read audition"); - assert!(read_audition.genre == default_audition.genre, "Failed to read audition genre"); - assert!(read_audition.name == default_audition.name, "Failed to read audition name"); - assert!( - read_audition.start_timestamp == default_audition.start_timestamp, - "Failed to read audition start timestamp", - ); - assert!( - read_audition.end_timestamp == default_audition.end_timestamp, - "Failed to read audition end timestamp", - ); - assert!(!read_audition.paused, "Failed to read audition paused"); - - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::AuditionCreated( - SeasonAndAudition::AuditionCreated { - audition_id: default_audition.audition_id, - season_id: default_audition.season_id, - genre: default_audition.genre, - name: default_audition.name, - }, - ), - ), - ], - ); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_update_audition() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: true, - }; - contract.update_audition(audition_id, updated_audition); - - // READ Updated Audition - let read_updated_audition = contract.read_audition(audition_id); - - assert!(read_updated_audition.genre == 'Rock', "Failed to update audition"); - assert!(read_updated_audition.name == 'Summer Audition', "Failed to update audition name"); - assert!(read_updated_audition.paused, "Failed to update audition paused"); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_delete_audition() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // DELETE Audition - contract.delete_audition(audition_id); - - // READ Deleted Audition - let deleted_audition = contract.read_audition(audition_id); - - assert!(deleted_audition.name == '', "Failed to delete audition"); - assert!(deleted_audition.genre == '', "Failed to delete audition genre"); - assert!(deleted_audition.start_timestamp == 0, "Failed to delete audition start timestamp"); - assert!(deleted_audition.end_timestamp == 0, "Failed to delete audition end timestamp"); - assert!(!deleted_audition.paused, "Failed to delete audition paused"); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_all_crud_operations() { - let (contract, _, _) = deploy_contract(); - - // Define season and audition IDs - let season_id: felt252 = 1; - let audition_id: felt252 = 1; - - // Create default season and audition - let default_season = create_default_season(season_id); - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Season - contract - .create_season( - season_id, - default_season.genre, - default_season.name, - default_season.start_timestamp, - default_season.end_timestamp, - default_season.paused, - ); - - // READ Season - let read_season = contract.read_season(season_id); - - println!("Default season is {}", default_season.paused); - - assert!(read_season.season_id == season_id, "Failed to read season"); - - // UPDATE Season - let updated_season = Season { - season_id, - genre: 'Rock', - name: 'Summer Hits', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: true, - }; - contract.update_season(season_id, updated_season); - let read_updated_season = contract.read_season(season_id); - - assert!(read_updated_season.genre == 'Rock', "Failed to update season"); - assert!(read_updated_season.name == 'Summer Hits', "Failed to update season name"); - assert!(read_updated_season.paused, "Failed to update season paused"); - - // DELETE Season - contract.delete_season(season_id); - let deleted_season = contract.read_season(season_id); - - assert!(deleted_season.name == 0, "Failed to delete season"); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // READ Audition - let read_audition = contract.read_audition(audition_id); - - assert!(read_audition.audition_id == audition_id, "Failed to read audition"); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1675123200, - paused: false //can't operate more functions if audition is paused - }; - contract.update_audition(audition_id, updated_audition); - let read_updated_audition = contract.read_audition(audition_id); - - assert!(read_updated_audition.genre == 'Rock', "Failed to update audition"); - assert!(read_updated_audition.name == 'Summer Audition', "Failed to update audition name"); - assert!(!read_updated_audition.paused, "Failed to update audition paused"); - - // DELETE Audition - contract.delete_audition(audition_id); - let deleted_audition = contract.read_audition(audition_id); - - assert!(deleted_audition.name == 0, "Failed to delete audition"); - - // Stop prank - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -#[feature("safe_dispatcher")] -fn test_safe_painc_only_owner_can_call_functions() { - let (_, _, safe_dispatcher) = deploy_contract(); - - // Start prank to simulate a non-owner calling the contract - start_cheat_caller_address(safe_dispatcher.contract_address, USER()); - - // Attempt to create a season - match safe_dispatcher.create_season(1, 'Pop', 100, 1672531200, 1675123200, false) { - Result::Ok(_) => panic!("Expected panic, but got success"), - Result::Err(e) => assert(*e.at(0) == 'Caller is not the owner', *e.at(0)), - } -} - - -#[test] -fn test_pause_audition() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -fn test_emission_of_event_for_pause_audition() { - let (contract, _, _) = deploy_contract(); - let mut spy = spy_events(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - - // Pause audition - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::AuditionPaused( - SeasonAndAudition::AuditionPaused { audition_id: audition_id }, - ), - ), - ], - ); - - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -#[should_panic(expect: 'Caller is not the owner')] -fn test_pause_audition_as_non_owner() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, NON_OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -#[should_panic(expect: 'Audition is already paused')] -fn test_pause_audition_twice_should_fail() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - stop_cheat_caller_address(contract.contract_address); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - // try to pause again - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -#[should_panic(expect: 'Cannot update paused audition')] -fn test_function_should_fail_after_pause_audition() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - // try to perform function - - // Delete Audition - contract.delete_audition(audition_id); - - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -fn test_resume_audition() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - //resume_audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.resume_audition(audition_id); - - //check that contract is no longer paused - let is_audition_pausedv2 = contract.read_audition(audition_id); - assert(!is_audition_pausedv2.paused, 'Audition is still paused'); - - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -#[should_panic(expect: 'Caller is not the owner')] -fn test_attempt_resume_audition_as_non_owner() { - let (contract, _, _) = deploy_contract(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - //resume_audition - start_cheat_caller_address(contract.contract_address, NON_OWNER()); - contract.resume_audition(audition_id); - - //check that contract is no longer paused - let is_audition_pausedv2 = contract.read_audition(audition_id); - assert(!is_audition_pausedv2.paused, 'Audition is still paused'); - - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_emission_of_event_for_resume_audition() { - let (contract, _, _) = deploy_contract(); - - let mut spy = spy_events(); - - // Define audition ID and season ID - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - // Create default audition - let default_audition = create_default_audition(audition_id, season_id); - - // Start prank to simulate the owner calling the contract - start_cheat_caller_address(contract.contract_address, OWNER()); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672531500, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - stop_cheat_caller_address(contract.contract_address); - - // Pause audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.pause_audition(audition_id); - - // check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - assert(is_audition_paused.paused, 'Audition is stil not paused'); - - //resume_audition - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.resume_audition(audition_id); - - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::AuditionResumed( - SeasonAndAudition::AuditionResumed { audition_id: audition_id }, - ), - ), - ], - ); - - //check that contract is no longer paused - let is_audition_pausedv2 = contract.read_audition(audition_id); - assert(!is_audition_pausedv2.paused, 'Audition is still paused'); - - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -fn test_end_audition() { - let (contract, _, _) = deploy_contract(); - - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - start_cheat_caller_address(contract.contract_address, OWNER()); - - // Add timestamp cheat - let initial_timestamp: u64 = 1672531200; - start_cheat_block_timestamp(contract.contract_address, initial_timestamp); - - let default_audition = create_default_audition(audition_id, season_id); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition with future end time - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672617600, // Future time (24 hours later) - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - - // Verify audition is not ended initially - assert(!contract.is_audition_ended(audition_id), 'Should not be ended initially'); - - // Pause audition (no need to call start_cheat_caller_address again) - contract.pause_audition(audition_id); - - // Check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - assert(is_audition_paused.paused, 'Audition should be paused'); - - // End the audition - let end_result = contract.end_audition(audition_id); - assert(end_result, 'End audition should succeed'); - - // Check that audition has ended properly - let audition_has_ended = contract.read_audition(audition_id); - assert(contract.is_audition_ended(audition_id), 'Audition should be ended'); - assert(audition_has_ended.end_timestamp != 0, 'End timestamp should be set'); - assert(audition_has_ended.end_timestamp != 1672617600, 'Should not be original end time'); - - // Check that the global contract is not paused - let global_is_paused = contract.is_paused(); - assert(!global_is_paused, 'Global contract is paused'); - - stop_cheat_block_timestamp(contract.contract_address); - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_end_audition_as_non_owner() { - let (contract, _, _) = deploy_contract(); - - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - start_cheat_caller_address(contract.contract_address, OWNER()); - - // Add timestamp cheat - let initial_timestamp: u64 = 1672531200; - start_cheat_block_timestamp(contract.contract_address, initial_timestamp); - - let default_audition = create_default_audition(audition_id, season_id); - - // CREATE Audition as owner - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition as owner - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672617600, - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - - start_cheat_caller_address(contract.contract_address, NON_OWNER()); - - contract.end_audition(audition_id); - - stop_cheat_block_timestamp(contract.contract_address); - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_emission_of_event_for_end_audition() { - let (contract, _, _) = deploy_contract(); - let mut spy = spy_events(); - - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - start_cheat_caller_address(contract.contract_address, OWNER()); - - // Add timestamp cheat - let initial_timestamp: u64 = 1672531200; - start_cheat_block_timestamp(contract.contract_address, initial_timestamp); - - let default_audition = create_default_audition(audition_id, season_id); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE Audition - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672617600, // Future time - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - - // Pause audition - contract.pause_audition(audition_id); - - // Check that the audition is paused - let is_audition_paused = contract.read_audition(audition_id); - assert(is_audition_paused.paused, 'Audition should be paused'); - - // End the audition - let end_result = contract.end_audition(audition_id); - assert(end_result, 'End audition should succeed'); - - // Check that audition has ended properly - let audition_has_ended = contract.read_audition(audition_id); - assert(contract.is_audition_ended(audition_id), 'Audition should be ended'); - assert(audition_has_ended.end_timestamp != 0, 'End timestamp should be set'); - - // Check event emission - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::AuditionEnded( - SeasonAndAudition::AuditionEnded { audition_id: audition_id }, - ), - ), - ], - ); - - stop_cheat_block_timestamp(contract.contract_address); - stop_cheat_caller_address(contract.contract_address); -} - - -#[test] -#[should_panic(expect: 'Cannot delete ended audition')] -fn test_end_audition_functionality() { - let (contract, _, _) = deploy_contract(); - - let audition_id: felt252 = 1; - let season_id: felt252 = 1; - - start_cheat_caller_address(contract.contract_address, OWNER()); - - // Set timestamp - let initial_timestamp: u64 = 1672531200; - start_cheat_block_timestamp(contract.contract_address, initial_timestamp); - - let default_audition = create_default_audition(audition_id, season_id); - - // CREATE Audition - contract - .create_audition( - audition_id, - season_id, - default_audition.genre, - default_audition.name, - default_audition.start_timestamp, - default_audition.end_timestamp, - default_audition.paused, - ); - - // UPDATE with future end time - let updated_audition = Audition { - audition_id, - season_id, - genre: 'Rock', - name: 'Summer Audition', - start_timestamp: 1672531200, - end_timestamp: 1672617600, // Future time - paused: false, - }; - contract.update_audition(audition_id, updated_audition); - - // Verify audition is not ended initially - assert(!contract.is_audition_ended(audition_id), 'Should not be ended initially'); - - // End the audition - let end_result = contract.end_audition(audition_id); - assert(end_result, 'End audition should succeed'); - - // Check state after ending - let audition_after_end = contract.read_audition(audition_id); - - // check that the audition has ended - assert(contract.is_audition_ended(audition_id), 'Audition should be ended'); - - assert(contract.is_audition_ended(audition_id), 'Audition should be ended'); - assert(audition_after_end.end_timestamp != 0, 'End timestamp should be set'); - assert(audition_after_end.end_timestamp != 1672617600, 'Should not be original end time'); - assert(audition_after_end.end_timestamp != 0, 'End timestamp should not be 0'); - - // Test restrictions on ended audition - //try to delete - contract.delete_audition(audition_id); - - println!("All tests passed!"); - - stop_cheat_block_timestamp(contract.contract_address); - stop_cheat_caller_address(contract.contract_address); -} diff --git a/contract_/tests/test_vote_recording.cairo b/contract_/tests/test_vote_recording.cairo deleted file mode 100644 index aeafe3ad..00000000 --- a/contract_/tests/test_vote_recording.cairo +++ /dev/null @@ -1,264 +0,0 @@ -use contract_::audition::season_and_audition::{ - ISeasonAndAuditionDispatcher, ISeasonAndAuditionDispatcherTrait, - ISeasonAndAuditionSafeDispatcher, SeasonAndAudition, -}; -use openzeppelin::access::ownable::interface::IOwnableDispatcher; -use snforge_std::{ - ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, - start_cheat_caller_address, stop_cheat_caller_address, -}; -use starknet::ContractAddress; - -// Test accounts -fn OWNER() -> ContractAddress { - 'OWNER'.try_into().unwrap() -} - -fn ORACLE() -> ContractAddress { - 'ORACLE'.try_into().unwrap() -} - -fn VOTER1() -> ContractAddress { - 'VOTER1'.try_into().unwrap() -} - -fn VOTER2() -> ContractAddress { - 'VOTER2'.try_into().unwrap() -} - -// Helper function to deploy the contract -fn deploy_contract() -> ( - ISeasonAndAuditionDispatcher, IOwnableDispatcher, ISeasonAndAuditionSafeDispatcher, -) { - // declare the contract - let contract_class = declare("SeasonAndAudition") - .expect('Failed to declare contract') - .contract_class(); - - // serialize constructor - let mut calldata: Array = array![]; - OWNER().serialize(ref calldata); - - // deploy the contract - let (contract_address, _) = contract_class - .deploy(@calldata) - .expect('Failed to deploy contract'); - - let contract = ISeasonAndAuditionDispatcher { contract_address }; - let ownable = IOwnableDispatcher { contract_address }; - let safe_dispatcher = ISeasonAndAuditionSafeDispatcher { contract_address }; - - (contract, ownable, safe_dispatcher) -} - -// Helper function to setup contract with oracle -fn setup_contract_with_oracle() -> ISeasonAndAuditionDispatcher { - let (contract, _, _) = deploy_contract(); - - // Add oracle - start_cheat_caller_address(contract.contract_address, OWNER()); - contract.add_oracle(ORACLE()); - stop_cheat_caller_address(contract.contract_address); - - contract -} - -// Helper function to create a default audition -fn create_test_audition(contract: ISeasonAndAuditionDispatcher, audition_id: felt252) { - start_cheat_caller_address(contract.contract_address, OWNER()); - contract - .create_audition( - audition_id, - 1, // season_id - 'Pop', - 'Test Audition', - 1672531200, // start_timestamp - 1675123200, // end_timestamp - false // paused - ); - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_record_vote_success() { - let contract = setup_contract_with_oracle(); - let mut spy = spy_events(); - - let audition_id: felt252 = 1; - let performer: felt252 = 'performer1'; - let voter: felt252 = 'voter1'; - let weight: felt252 = 100; - - // Create audition first - create_test_audition(contract, audition_id); - - // Record vote as oracle - start_cheat_caller_address(contract.contract_address, ORACLE()); - contract.record_vote(audition_id, performer, voter, weight); - stop_cheat_caller_address(contract.contract_address); - - // Verify vote was recorded - start_cheat_caller_address(contract.contract_address, OWNER()); - let recorded_vote = contract.get_vote(audition_id, performer, voter); - stop_cheat_caller_address(contract.contract_address); - - assert!(recorded_vote.audition_id == audition_id, "Audition ID mismatch"); - assert!(recorded_vote.performer == performer, "Performer mismatch"); - assert!(recorded_vote.voter == voter, "Voter mismatch"); - assert!(recorded_vote.weight == weight, "Weight mismatch"); - - // Verify event was emitted - spy - .assert_emitted( - @array![ - ( - contract.contract_address, - SeasonAndAudition::Event::VoteRecorded( - SeasonAndAudition::VoteRecorded { audition_id, performer, voter, weight }, - ), - ), - ], - ); -} - -#[test] -#[should_panic(expected: ('Vote already exists',))] -fn test_record_vote_duplicate_should_fail() { - let contract = setup_contract_with_oracle(); - - let audition_id: felt252 = 1; - let performer: felt252 = 'performer1'; - let voter: felt252 = 'voter1'; - let weight: felt252 = 100; - - // Create audition first - create_test_audition(contract, audition_id); - - // Record first vote as oracle - start_cheat_caller_address(contract.contract_address, ORACLE()); - contract.record_vote(audition_id, performer, voter, weight); - - // Try to record duplicate vote - should fail - contract.record_vote(audition_id, performer, voter, weight); - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -#[should_panic(expected: ('Not Authorized',))] -fn test_record_vote_unauthorized_should_fail() { - let contract = setup_contract_with_oracle(); - - let audition_id: felt252 = 1; - let performer: felt252 = 'performer1'; - let voter: felt252 = 'voter1'; - let weight: felt252 = 100; - - // Create audition first - create_test_audition(contract, audition_id); - - // Try to record vote as non-oracle - should fail - start_cheat_caller_address(contract.contract_address, VOTER1()); - contract.record_vote(audition_id, performer, voter, weight); - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_record_multiple_votes_different_combinations() { - let contract = setup_contract_with_oracle(); - - let audition_id: felt252 = 1; - let performer1: felt252 = 'performer1'; - let performer2: felt252 = 'performer2'; - let voter1: felt252 = 'voter1'; - let voter2: felt252 = 'voter2'; - let weight: felt252 = 100; - - // Create audition first - create_test_audition(contract, audition_id); - - start_cheat_caller_address(contract.contract_address, ORACLE()); - - // Record vote: voter1 -> performer1 - contract.record_vote(audition_id, performer1, voter1, weight); - - // Record vote: voter1 -> performer2 (same voter, different performer - should work) - contract.record_vote(audition_id, performer2, voter1, weight); - - // Record vote: voter2 -> performer1 (different voter, same performer - should work) - contract.record_vote(audition_id, performer2, voter2, weight); - - stop_cheat_caller_address(contract.contract_address); - - // Verify all votes were recorded - start_cheat_caller_address(contract.contract_address, OWNER()); - - let vote1 = contract.get_vote(audition_id, performer1, voter1); - assert!(vote1.audition_id == audition_id, "Vote1 not recorded"); - - let vote2 = contract.get_vote(audition_id, performer2, voter1); - assert!(vote2.audition_id == audition_id, "Vote2 not recorded"); - - let vote3 = contract.get_vote(audition_id, performer2, voter2); - assert!(vote3.audition_id == audition_id, "Vote3 not recorded"); - - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_record_votes_different_auditions() { - let contract = setup_contract_with_oracle(); - - let audition_id1: felt252 = 1; - let audition_id2: felt252 = 2; - let performer: felt252 = 'performer1'; - let voter: felt252 = 'voter1'; - let weight: felt252 = 100; - - // Create auditions first - create_test_audition(contract, audition_id1); - create_test_audition(contract, audition_id2); - - start_cheat_caller_address(contract.contract_address, ORACLE()); - - // Record vote for audition 1 - contract.record_vote(audition_id1, performer, voter, weight); - - // Record vote for audition 2 (same performer and voter, but different audition - should work) - contract.record_vote(audition_id2, performer, voter, weight); - - stop_cheat_caller_address(contract.contract_address); - - // Verify both votes were recorded - start_cheat_caller_address(contract.contract_address, OWNER()); - - let vote1 = contract.get_vote(audition_id1, performer, voter); - assert!(vote1.audition_id == audition_id1, "Vote1 not recorded"); - - let vote2 = contract.get_vote(audition_id2, performer, voter); - assert!(vote2.audition_id == audition_id2, "Vote2 not recorded"); - - stop_cheat_caller_address(contract.contract_address); -} - -#[test] -fn test_get_vote_nonexistent_returns_default() { - let contract = setup_contract_with_oracle(); - - let audition_id: felt252 = 1; - let performer: felt252 = 'performer1'; - let voter: felt252 = 'voter1'; - - // Create audition first - create_test_audition(contract, audition_id); - - // Try to get non-existent vote - start_cheat_caller_address(contract.contract_address, OWNER()); - let vote = contract.get_vote(audition_id, performer, voter); - stop_cheat_caller_address(contract.contract_address); - - // Should return default vote (all zeros) - assert!(vote.audition_id == 0, "Should return default vote"); - assert!(vote.performer == 0, "Should return default vote"); - assert!(vote.voter == 0, "Should return default vote"); - assert!(vote.weight == 0, "Should return default vote"); -}