From eef4ab0c50d5cf4afe97f4a33643492aaf06bdb6 Mon Sep 17 00:00:00 2001 From: 1nonlypiece Date: Mon, 7 Jul 2025 22:08:53 +0530 Subject: [PATCH 1/3] feat: Implement comprehensive admin management system in Predictify Hybrid contract, including role assignments, permission validation, action logging, and event emissions, enhancing administrative control and oversight capabilities --- contracts/predictify-hybrid/src/admin.rs | 931 +++++++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 contracts/predictify-hybrid/src/admin.rs diff --git a/contracts/predictify-hybrid/src/admin.rs b/contracts/predictify-hybrid/src/admin.rs new file mode 100644 index 00000000..6a774050 --- /dev/null +++ b/contracts/predictify-hybrid/src/admin.rs @@ -0,0 +1,931 @@ +use soroban_sdk::{contracttype, symbol_short, vec, Address, Env, Map, String, Symbol, Vec}; +use alloc::string::ToString; +use alloc::format; + +use crate::errors::Error; +use crate::events::{EventEmitter, AdminActionEvent, AdminRoleEvent, AdminPermissionEvent, MarketClosedEvent, MarketFinalizedEvent, AdminInitializedEvent, ConfigInitializedEvent}; +use crate::markets::{MarketStateManager, MarketValidator}; +use crate::fees::{FeeManager, FeeConfig}; +use crate::config::{ConfigManager, ContractConfig, Environment, ConfigUtils}; +use crate::resolution::MarketResolutionManager; +use crate::extensions::ExtensionManager; +use crate::types::*; + +/// Admin management system for Predictify Hybrid contract +/// +/// This module provides a comprehensive admin system with: +/// - Admin initialization and setup functions +/// - Access control and permission validation +/// - Admin role management and hierarchy +/// - Admin action logging and tracking +/// - Admin helper utilities and testing functions +/// - Admin event emission and monitoring + +// ===== ADMIN TYPES ===== + +/// Admin role enumeration +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[contracttype] +pub enum AdminRole { + /// Super admin with all permissions + SuperAdmin, + /// Market admin with market management permissions + MarketAdmin, + /// Config admin with configuration permissions + ConfigAdmin, + /// Fee admin with fee management permissions + FeeAdmin, + /// Read-only admin with view permissions only + ReadOnlyAdmin, +} + +/// Admin permission enumeration +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[contracttype] +pub enum AdminPermission { + /// Initialize contract + Initialize, + /// Create markets + CreateMarket, + /// Close markets + CloseMarket, + /// Finalize markets + FinalizeMarket, + /// Extend market duration + ExtendMarket, + /// Update fee configuration + UpdateFees, + /// Update contract configuration + UpdateConfig, + /// Reset configuration + ResetConfig, + /// Collect fees + CollectFees, + /// Manage disputes + ManageDisputes, + /// View analytics + ViewAnalytics, + /// Emergency actions + EmergencyActions, +} + +/// Admin action record +#[derive(Clone, Debug)] +#[contracttype] +pub struct AdminAction { + pub admin: Address, + pub action: String, + pub target: Option, + pub parameters: Map, + pub timestamp: u64, + pub success: bool, + pub error_message: Option, +} + +/// Admin role assignment +#[derive(Clone, Debug)] +#[contracttype] +pub struct AdminRoleAssignment { + pub admin: Address, + pub role: AdminRole, + pub assigned_by: Address, + pub assigned_at: u64, + pub permissions: Vec, + pub is_active: bool, +} + +/// Admin analytics +#[derive(Clone, Debug)] +#[contracttype] +pub struct AdminAnalytics { + pub total_admins: u32, + pub active_admins: u32, + pub total_actions: u32, + pub successful_actions: u32, + pub failed_actions: u32, + pub action_distribution: Map, + pub role_distribution: Map, + pub recent_actions: Vec, +} + +// ===== ADMIN INITIALIZATION ===== + +/// Admin initialization management +pub struct AdminInitializer; + +impl AdminInitializer { + /// Initialize contract with admin + pub fn initialize(env: &Env, admin: &Address) -> Result<(), Error> { + // Validate admin address + AdminValidator::validate_admin_address(env, admin)?; + + // Store admin in persistent storage + env.storage() + .persistent() + .set(&Symbol::new(env, "Admin"), admin); + + // Set default admin role + AdminRoleManager::assign_role( + env, + admin, + AdminRole::SuperAdmin, + admin, + )?; + + // Emit admin initialization event + EventEmitter::emit_admin_initialized(env, admin); + + // Log admin action + AdminActionLogger::log_action( + env, + admin, + "initialize", + None, + Map::new(env), + true, + None, + )?; + + Ok(()) + } + + /// Initialize contract with configuration + pub fn initialize_with_config( + env: &Env, + admin: &Address, + environment: &Environment, + ) -> Result<(), Error> { + // Initialize basic admin setup + AdminInitializer::initialize(env, admin)?; + + let config = match environment { + Environment::Development => ConfigManager::get_development_config(env), + Environment::Testnet => ConfigManager::get_testnet_config(env), + Environment::Mainnet => ConfigManager::get_mainnet_config(env), + Environment::Custom => ConfigManager::get_development_config(env), + }; + ConfigManager::store_config(env, &config)?; + + // Emit configuration initialization event + EventEmitter::emit_config_initialized(env, admin, environment); + + Ok(()) + } + + /// Validate initialization parameters + pub fn validate_initialization_params( + env: &Env, + admin: &Address, + ) -> Result<(), Error> { + AdminValidator::validate_admin_address(env, admin)?; + AdminValidator::validate_contract_not_initialized(env)?; + Ok(()) + } +} + +// ===== ADMIN ACCESS CONTROL ===== + +/// Admin access control management +pub struct AdminAccessControl; + +impl AdminAccessControl { + /// Validate admin permissions for an action + pub fn validate_permission( + env: &Env, + admin: &Address, + permission: &AdminPermission, + ) -> Result<(), Error> { + // Get admin role + let role = AdminRoleManager::get_admin_role(env, admin)?; + + // Check if admin has the required permission + if !AdminRoleManager::has_permission(env, &role, permission)? { + return Err(Error::Unauthorized); + } + + Ok(()) + } + + /// Require admin authentication + pub fn require_admin_auth(env: &Env, admin: &Address) -> Result<(), Error> { + // Verify admin authentication + admin.require_auth(); + + // Validate admin exists + let stored_admin: Address = env + .storage() + .persistent() + .get(&Symbol::new(env, "Admin")) + .ok_or(Error::AdminNotSet)?; + + if admin != &stored_admin { + return Err(Error::Unauthorized); + } + + Ok(()) + } + + /// Validate admin for specific action + pub fn validate_admin_for_action( + env: &Env, + admin: &Address, + action: &str, + ) -> Result<(), Error> { + // Require admin authentication + AdminAccessControl::require_admin_auth(env, admin)?; + + // Map action to permission + let permission = AdminAccessControl::map_action_to_permission(action)?; + + // Validate permission + AdminAccessControl::validate_permission(env, admin, &permission)?; + + Ok(()) + } + + /// Map action string to permission enum + pub fn map_action_to_permission(action: &str) -> Result { + match action { + "initialize" => Ok(AdminPermission::Initialize), + "create_market" => Ok(AdminPermission::CreateMarket), + "close_market" => Ok(AdminPermission::CloseMarket), + "finalize_market" => Ok(AdminPermission::FinalizeMarket), + "extend_market" => Ok(AdminPermission::ExtendMarket), + "update_fees" => Ok(AdminPermission::UpdateFees), + "update_config" => Ok(AdminPermission::UpdateConfig), + "reset_config" => Ok(AdminPermission::ResetConfig), + "collect_fees" => Ok(AdminPermission::CollectFees), + "manage_disputes" => Ok(AdminPermission::ManageDisputes), + "view_analytics" => Ok(AdminPermission::ViewAnalytics), + "emergency_actions" => Ok(AdminPermission::EmergencyActions), + _ => Err(Error::InvalidInput), + } + } +} + +// ===== ADMIN ROLE MANAGEMENT ===== + +/// Admin role management +pub struct AdminRoleManager; + +impl AdminRoleManager { + /// Assign role to admin + pub fn assign_role( + env: &Env, + admin: &Address, + role: AdminRole, + assigned_by: &Address, + ) -> Result<(), Error> { + // Validate assigner permissions + AdminAccessControl::validate_permission( + env, + assigned_by, + &AdminPermission::EmergencyActions, + )?; + + // Create role assignment + let assignment = AdminRoleAssignment { + admin: admin.clone(), + role, + assigned_by: assigned_by.clone(), + assigned_at: env.ledger().timestamp(), + permissions: AdminRoleManager::get_permissions_for_role(&role), + is_active: true, + }; + + // Store role assignment + let key = Symbol::new(env, "admin_role"); + env.storage().persistent().set(&key, &assignment); + + // Emit role assignment event + EventEmitter::emit_admin_role_assigned(env, admin, &role, assigned_by); + + Ok(()) + } + + /// Get admin role + pub fn get_admin_role(env: &Env, admin: &Address) -> Result { + let key = Symbol::new(env, "admin_role"); + let assignment: AdminRoleAssignment = env + .storage() + .persistent() + .get(&key) + .ok_or(Error::Unauthorized)?; + + if !assignment.is_active { + return Err(Error::Unauthorized); + } + + Ok(assignment.role) + } + + /// Check if admin has permission + pub fn has_permission( + env: &Env, + role: &AdminRole, + permission: &AdminPermission, + ) -> Result { + let permissions = AdminRoleManager::get_permissions_for_role(role); + Ok(permissions.contains(permission)) + } + + /// Get permissions for role + pub fn get_permissions_for_role(role: &AdminRole) -> Vec { + let env = soroban_sdk::Env::default(); + match role { + AdminRole::SuperAdmin => vec![ + &env, + AdminPermission::Initialize, + AdminPermission::CreateMarket, + AdminPermission::CloseMarket, + AdminPermission::FinalizeMarket, + AdminPermission::ExtendMarket, + AdminPermission::UpdateFees, + AdminPermission::UpdateConfig, + AdminPermission::ResetConfig, + AdminPermission::CollectFees, + AdminPermission::ManageDisputes, + AdminPermission::ViewAnalytics, + AdminPermission::EmergencyActions, + ], + AdminRole::MarketAdmin => vec![ + &env, + AdminPermission::CreateMarket, + AdminPermission::CloseMarket, + AdminPermission::FinalizeMarket, + AdminPermission::ExtendMarket, + AdminPermission::ViewAnalytics, + ], + AdminRole::ConfigAdmin => vec![ + &env, + AdminPermission::UpdateConfig, + AdminPermission::ResetConfig, + AdminPermission::ViewAnalytics, + ], + AdminRole::FeeAdmin => vec![ + &env, + AdminPermission::UpdateFees, + AdminPermission::CollectFees, + AdminPermission::ViewAnalytics, + ], + AdminRole::ReadOnlyAdmin => vec![ + &env, + AdminPermission::ViewAnalytics, + ], + } + } + + /// Deactivate admin role + pub fn deactivate_role( + env: &Env, + admin: &Address, + deactivated_by: &Address, + ) -> Result<(), Error> { + // Validate deactivator permissions + AdminAccessControl::validate_permission( + env, + deactivated_by, + &AdminPermission::EmergencyActions, + )?; + + let key = Symbol::new(env, "admin_role"); + let mut assignment: AdminRoleAssignment = env + .storage() + .persistent() + .get(&key) + .ok_or(Error::Unauthorized)?; + + assignment.is_active = false; + env.storage().persistent().set(&key, &assignment); + + // Emit role deactivation event + EventEmitter::emit_admin_role_deactivated(env, admin, deactivated_by); + + Ok(()) + } +} + +// ===== ADMIN FUNCTIONS ===== + +/// Admin function management +pub struct AdminFunctions; + +impl AdminFunctions { + /// Close market (admin only) + pub fn close_market( + env: &Env, + admin: &Address, + market_id: &Symbol, + ) -> Result<(), Error> { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "close_market")?; + + // Get market + let market = MarketStateManager::get_market(env, market_id)?; + + // Close market + MarketStateManager::remove_market(env, market_id); + + // Emit market closed event + EventEmitter::emit_market_closed(env, market_id, admin); + + // Log admin action + let mut params = Map::new(env); + params.set(String::from_str(env, "market_id"), String::from_str(env, &market_id.to_string())); + AdminActionLogger::log_action(env, admin, "close_market", None, params, true, None)?; + + Ok(()) + } + + /// Finalize market with admin override + pub fn finalize_market( + env: &Env, + admin: &Address, + market_id: &Symbol, + outcome: &String, + ) -> Result<(), Error> { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "finalize_market")?; + + // Finalize market using resolution manager + let resolution = MarketResolutionManager::finalize_market(env, admin, market_id, outcome)?; + + // Emit market finalized event + EventEmitter::emit_market_finalized(env, market_id, admin, outcome); + + // Log admin action + let mut params = Map::new(env); + params.set(String::from_str(env, "market_id"), String::from_str(env, &market_id.to_string())); + params.set(String::from_str(env, "outcome"), outcome.clone()); + AdminActionLogger::log_action(env, admin, "finalize_market", Some(String::from_str(env, &market_id.to_string())), params, true, None)?; + + Ok(()) + } + + /// Extend market duration + pub fn extend_market_duration( + env: &Env, + admin: &Address, + market_id: &Symbol, + additional_days: u32, + reason: &String, + ) -> Result<(), Error> { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "extend_market")?; + + // Extend market using extension manager + ExtensionManager::extend_market_duration(env, admin.clone(), market_id.clone(), additional_days, reason.clone())?; + + // Log admin action + let mut params = Map::new(env); + params.set(String::from_str(env, "market_id"), String::from_str(env, &market_id.to_string())); + params.set(String::from_str(env, "additional_days"), String::from_str(env, &additional_days.to_string())); + params.set(String::from_str(env, "reason"), reason.clone()); + AdminActionLogger::log_action(env, admin, "extend_market", Some(String::from_str(env, &market_id.to_string())), params, true, None)?; + + Ok(()) + } + + /// Update fee configuration + pub fn update_fee_config( + env: &Env, + admin: &Address, + new_config: &FeeConfig, + ) -> Result { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "update_fees")?; + + // Update fee configuration + let updated_config = FeeManager::update_fee_config(env, admin.clone(), new_config.clone())?; + + // Log admin action + let mut params = Map::new(env); + params.set(String::from_str(env, "platform_fee"), String::from_str(env, &new_config.platform_fee_percentage.to_string())); + params.set(String::from_str(env, "creation_fee"), String::from_str(env, &new_config.creation_fee.to_string())); + AdminActionLogger::log_action(env, admin, "update_fees", None, params, true, None)?; + + Ok(updated_config) + } + + /// Update contract configuration + pub fn update_contract_config( + env: &Env, + admin: &Address, + new_config: &ContractConfig, + ) -> Result<(), Error> { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "update_config")?; + + // Update contract configuration + ConfigManager::update_config(env, &new_config)?; + let env_name = ConfigUtils::get_environment_name(&new_config); + let mut params = Map::new(env); + params.set(String::from_str(env, "environment"), env_name); + AdminActionLogger::log_action(env, admin, "update_config", None, params, true, None)?; + + Ok(()) + } + + /// Reset configuration to defaults + pub fn reset_config_to_defaults( + env: &Env, + admin: &Address, + ) -> Result { + // Validate admin permissions + AdminAccessControl::validate_admin_for_action(env, admin, "reset_config")?; + + // Reset configuration + let default_config = ConfigManager::reset_to_defaults(env)?; + + // Log admin action + AdminActionLogger::log_action(env, admin, "reset_config", None, Map::new(env), true, None)?; + + Ok(default_config) + } +} + +// ===== ADMIN VALIDATION ===== + +/// Admin validation utilities +pub struct AdminValidator; + +impl AdminValidator { + /// Validate admin address + pub fn validate_admin_address(env: &Env, admin: &Address) -> Result<(), Error> { + // Check if address is valid + if admin.to_string().is_empty() { + return Err(Error::InvalidInput); + } + + Ok(()) + } + + /// Validate contract not already initialized + pub fn validate_contract_not_initialized(env: &Env) -> Result<(), Error> { + let admin_exists = env + .storage() + .persistent() + .has(&Symbol::new(env, "Admin")); + + if admin_exists { + return Err(Error::InvalidState); + } + + Ok(()) + } + + /// Validate admin action parameters + pub fn validate_action_parameters( + env: &Env, + action: &str, + parameters: &Map, + ) -> Result<(), Error> { + match action { + "close_market" => { + let market_id = parameters.get(String::from_str(env, "market_id")) + .ok_or(Error::InvalidInput)?; + if market_id.is_empty() { + return Err(Error::InvalidInput); + } + } + "finalize_market" => { + let market_id = parameters.get(String::from_str(env, "market_id")) + .ok_or(Error::InvalidInput)?; + let outcome = parameters.get(String::from_str(env, "outcome")) + .ok_or(Error::InvalidInput)?; + if market_id.is_empty() || outcome.is_empty() { + return Err(Error::InvalidInput); + } + } + "extend_market" => { + let market_id = parameters.get(String::from_str(env, "market_id")) + .ok_or(Error::InvalidInput)?; + let additional_days = parameters.get(String::from_str(env, "additional_days")) + .ok_or(Error::InvalidInput)?; + if market_id.is_empty() || additional_days.is_empty() { + return Err(Error::InvalidInput); + } + } + _ => {} + } + + Ok(()) + } +} + +// ===== ADMIN ACTION LOGGING ===== + +/// Admin action logging +pub struct AdminActionLogger; + +impl AdminActionLogger { + /// Log admin action + pub fn log_action( + env: &Env, + admin: &Address, + action: &str, + target: Option, + parameters: Map, + success: bool, + error_message: Option, + ) -> Result<(), Error> { + let admin_action = AdminAction { + admin: admin.clone(), + action: String::from_str(env, action), + target, + parameters, + timestamp: env.ledger().timestamp(), + success, + error_message, + }; + + // Store action in persistent storage + let action_key = Symbol::new(env, "admin_action"); + env.storage().persistent().set(&action_key, &admin_action); + + // Emit admin action event + EventEmitter::emit_admin_action_logged(env, admin, action, &success); + + Ok(()) + } + + /// Get admin actions + pub fn get_admin_actions(env: &Env, limit: u32) -> Result, Error> { + // For now, return empty vector since we don't have a way to iterate over storage + // In a real implementation, you would store actions in a more sophisticated way + Ok(Vec::new(env)) + } + + /// Get admin actions for specific admin + pub fn get_admin_actions_for_admin( + env: &Env, + admin: &Address, + limit: u32, + ) -> Result, Error> { + // For now, return empty vector + Ok(Vec::new(env)) + } +} + +// ===== ADMIN ANALYTICS ===== + +/// Admin analytics +impl AdminAnalytics { + /// Calculate admin analytics + pub fn calculate_admin_analytics(env: &Env) -> Result { + // For now, return default analytics since we don't store complex types + Ok(AdminAnalytics::default()) + } + + /// Get admin role distribution + pub fn get_role_distribution(env: &Env) -> Result, Error> { + // For now, return empty map + Ok(Map::new(env)) + } + + /// Get action distribution + pub fn get_action_distribution(env: &Env) -> Result, Error> { + // For now, return empty map + Ok(Map::new(env)) + } +} + +// ===== ADMIN UTILITIES ===== + +/// Admin utility functions +pub struct AdminUtils; + +impl AdminUtils { + /// Check if address is admin + pub fn is_admin(env: &Env, address: &Address) -> bool { + AdminRoleManager::get_admin_role(env, address).is_ok() + } + + /// Check if address is super admin + pub fn is_super_admin(env: &Env, address: &Address) -> bool { + match AdminRoleManager::get_admin_role(env, address) { + Ok(role) => role == AdminRole::SuperAdmin, + Err(_) => false, + } + } + + /// Get admin role name + pub fn get_role_name(role: &AdminRole) -> String { + match role { + AdminRole::SuperAdmin => String::from_str(&soroban_sdk::Env::default(), "SuperAdmin"), + AdminRole::MarketAdmin => String::from_str(&soroban_sdk::Env::default(), "MarketAdmin"), + AdminRole::ConfigAdmin => String::from_str(&soroban_sdk::Env::default(), "ConfigAdmin"), + AdminRole::FeeAdmin => String::from_str(&soroban_sdk::Env::default(), "FeeAdmin"), + AdminRole::ReadOnlyAdmin => String::from_str(&soroban_sdk::Env::default(), "ReadOnlyAdmin"), + } + } + + /// Get permission name + pub fn get_permission_name(permission: &AdminPermission) -> String { + match permission { + AdminPermission::Initialize => String::from_str(&soroban_sdk::Env::default(), "Initialize"), + AdminPermission::CreateMarket => String::from_str(&soroban_sdk::Env::default(), "CreateMarket"), + AdminPermission::CloseMarket => String::from_str(&soroban_sdk::Env::default(), "CloseMarket"), + AdminPermission::FinalizeMarket => String::from_str(&soroban_sdk::Env::default(), "FinalizeMarket"), + AdminPermission::ExtendMarket => String::from_str(&soroban_sdk::Env::default(), "ExtendMarket"), + AdminPermission::UpdateFees => String::from_str(&soroban_sdk::Env::default(), "UpdateFees"), + AdminPermission::UpdateConfig => String::from_str(&soroban_sdk::Env::default(), "UpdateConfig"), + AdminPermission::ResetConfig => String::from_str(&soroban_sdk::Env::default(), "ResetConfig"), + AdminPermission::CollectFees => String::from_str(&soroban_sdk::Env::default(), "CollectFees"), + AdminPermission::ManageDisputes => String::from_str(&soroban_sdk::Env::default(), "ManageDisputes"), + AdminPermission::ViewAnalytics => String::from_str(&soroban_sdk::Env::default(), "ViewAnalytics"), + AdminPermission::EmergencyActions => String::from_str(&soroban_sdk::Env::default(), "EmergencyActions"), + } + } +} + +// ===== ADMIN TESTING ===== + +/// Admin testing utilities +pub struct AdminTesting; + +impl AdminTesting { + /// Create test admin action + pub fn create_test_admin_action(env: &Env, admin: &Address) -> AdminAction { + AdminAction { + admin: admin.clone(), + action: String::from_str(env, "test_action"), + target: Some(String::from_str(env, "test_target")), + parameters: Map::new(env), + timestamp: env.ledger().timestamp(), + success: true, + error_message: None, + } + } + + /// Create test admin role assignment + pub fn create_test_role_assignment(env: &Env, admin: &Address) -> AdminRoleAssignment { + AdminRoleAssignment { + admin: admin.clone(), + role: AdminRole::MarketAdmin, + assigned_by: admin.clone(), + assigned_at: env.ledger().timestamp(), + permissions: AdminRoleManager::get_permissions_for_role(&AdminRole::MarketAdmin), + is_active: true, + } + } + + /// Validate admin action structure + pub fn validate_admin_action_structure(action: &AdminAction) -> Result<(), Error> { + if action.action.is_empty() { + return Err(Error::InvalidInput); + } + + if action.timestamp == 0 { + return Err(Error::InvalidInput); + } + + Ok(()) + } + + /// Simulate admin action + pub fn simulate_admin_action( + env: &Env, + admin: &Address, + action: &str, + ) -> Result<(), Error> { + // Log test action + AdminActionLogger::log_action( + env, + admin, + action, + Some(String::from_str(env, "test_target")), + Map::new(env), + true, + None, + )?; + + Ok(()) + } +} + +// ===== DEFAULT IMPLEMENTATIONS ===== + +impl Default for AdminAnalytics { + fn default() -> Self { + let env = soroban_sdk::Env::default(); + Self { + total_admins: 0, + active_admins: 0, + total_actions: 0, + successful_actions: 0, + failed_actions: 0, + action_distribution: Map::new(&env), + role_distribution: Map::new(&env), + recent_actions: Vec::new(&env), + } + } +} + +// ===== MODULE TESTS ===== + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::{Address as _, Ledger, LedgerInfo}; + + #[test] + fn test_admin_initializer_initialize() { + let env = Env::default(); + let admin = Address::generate(&env); + + // Test initialization + assert!(AdminInitializer::initialize(&env, &admin).is_ok()); + + // Verify admin is stored + let stored_admin: Address = env + .storage() + .persistent() + .get(&Symbol::new(&env, "Admin")) + .unwrap(); + assert_eq!(stored_admin, admin); + } + + #[test] + fn test_admin_access_control_validate_permission() { + let env = Env::default(); + let admin = Address::generate(&env); + + // Initialize admin + AdminInitializer::initialize(&env, &admin).unwrap(); + + // Test permission validation + assert!(AdminAccessControl::validate_permission( + &env, + &admin, + &AdminPermission::CreateMarket + ).is_ok()); + } + + #[test] + fn test_admin_role_manager_assign_role() { + let env = Env::default(); + let admin = Address::generate(&env); + let new_admin = Address::generate(&env); + + // Initialize admin + AdminInitializer::initialize(&env, &admin).unwrap(); + + // Assign role + assert!(AdminRoleManager::assign_role( + &env, + &new_admin, + AdminRole::MarketAdmin, + &admin + ).is_ok()); + + // Verify role assignment + let role = AdminRoleManager::get_admin_role(&env, &new_admin).unwrap(); + assert_eq!(role, AdminRole::MarketAdmin); + } + + #[test] + fn test_admin_functions_close_market() { + let env = Env::default(); + let admin = Address::generate(&env); + let market_id = Symbol::new(&env, "test_market"); + + // Initialize admin + AdminInitializer::initialize(&env, &admin).unwrap(); + + // Test close market (would need a real market setup) + // For now, just test the validation + assert!(AdminAccessControl::validate_admin_for_action( + &env, + &admin, + "close_market" + ).is_ok()); + } + + #[test] + fn test_admin_utils_is_admin() { + let env = Env::default(); + let admin = Address::generate(&env); + let non_admin = Address::generate(&env); + + // Initialize admin + AdminInitializer::initialize(&env, &admin).unwrap(); + + // Test admin check + assert!(AdminUtils::is_admin(&env, &admin)); + assert!(!AdminUtils::is_admin(&env, &non_admin)); + } + + #[test] + fn test_admin_testing_utilities() { + let env = Env::default(); + let admin = Address::generate(&env); + + let action = AdminTesting::create_test_admin_action(&env, &admin); + assert!(AdminTesting::validate_admin_action_structure(&action).is_ok()); + + let role_assignment = AdminTesting::create_test_role_assignment(&env, &admin); + assert_eq!(role_assignment.role, AdminRole::MarketAdmin); + assert!(role_assignment.is_active); + } +} \ No newline at end of file From 0e2b89526dda9821f434ab2101af767394e49582 Mon Sep 17 00:00:00 2001 From: 1nonlypiece Date: Mon, 7 Jul 2025 22:08:59 +0530 Subject: [PATCH 2/3] feat: Extend event system in Predictify Hybrid contract with new admin and market events, enhancing administrative tracking and market management capabilities --- contracts/predictify-hybrid/src/events.rs | 205 ++++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/contracts/predictify-hybrid/src/events.rs b/contracts/predictify-hybrid/src/events.rs index ac1f72f8..0b57f256 100644 --- a/contracts/predictify-hybrid/src/events.rs +++ b/contracts/predictify-hybrid/src/events.rs @@ -1,7 +1,12 @@ +extern crate alloc; + use soroban_sdk::{contracttype, vec, symbol_short, Address, Env, Map, String, Symbol, Vec}; use alloc::string::ToString; +use alloc::format; use crate::errors::Error; +use crate::Environment; +use crate::admin::AdminRole; /// Comprehensive event system for Predictify Hybrid contract /// @@ -209,6 +214,98 @@ pub struct PerformanceMetricEvent { pub timestamp: u64, } +/// Admin action event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AdminActionEvent { + /// Admin address + pub admin: Address, + /// Action performed + pub action: String, + /// Target of action + pub target: Option, + /// Action timestamp + pub timestamp: u64, + /// Action success status + pub success: bool, +} + +/// Admin role event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AdminRoleEvent { + /// Admin address + pub admin: Address, + /// Role assigned + pub role: String, + /// Assigned by + pub assigned_by: Address, + /// Assignment timestamp + pub timestamp: u64, +} + +/// Admin permission event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AdminPermissionEvent { + /// Admin address + pub admin: Address, + /// Permission checked + pub permission: String, + /// Access granted + pub granted: bool, + /// Check timestamp + pub timestamp: u64, +} + +/// Market closed event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MarketClosedEvent { + /// Market ID + pub market_id: Symbol, + /// Admin who closed it + pub admin: Address, + /// Close timestamp + pub timestamp: u64, +} + +/// Market finalized event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MarketFinalizedEvent { + /// Market ID + pub market_id: Symbol, + /// Admin who finalized it + pub admin: Address, + /// Final outcome + pub outcome: String, + /// Finalization timestamp + pub timestamp: u64, +} + +/// Admin initialized event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AdminInitializedEvent { + /// Admin address + pub admin: Address, + /// Initialization timestamp + pub timestamp: u64, +} + +/// Config initialized event +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ConfigInitializedEvent { + /// Admin address + pub admin: Address, + /// Environment + pub environment: String, + /// Initialization timestamp + pub timestamp: u64, +} + // ===== EVENT EMISSION UTILITIES ===== /// Event emission utilities @@ -442,6 +539,114 @@ impl EventEmitter { Self::store_event(env, &symbol_short!("perf_met"), &event); } + /// Emit admin action logged event + pub fn emit_admin_action_logged( + env: &Env, + admin: &Address, + action: &str, + success: &bool, + ) { + let event = AdminActionEvent { + admin: admin.clone(), + action: String::from_str(env, action), + target: None, + timestamp: env.ledger().timestamp(), + success: *success, + }; + + Self::store_event(env, &symbol_short!("adm_act"), &event); + } + + /// Emit admin initialized event + pub fn emit_admin_initialized(env: &Env, admin: &Address) { + let event = AdminInitializedEvent { + admin: admin.clone(), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("adm_init"), &event); + } + + /// Emit config initialized event + pub fn emit_config_initialized( + env: &Env, + admin: &Address, + environment: &Environment, + ) { + let event = ConfigInitializedEvent { + admin: admin.clone(), + environment: String::from_str(env, &format!("{:?}", environment)), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("cfg_init"), &event); + } + + /// Emit admin role assigned event + pub fn emit_admin_role_assigned( + env: &Env, + admin: &Address, + role: &AdminRole, + assigned_by: &Address, + ) { + let event = AdminRoleEvent { + admin: admin.clone(), + role: String::from_str(env, &format!("{:?}", role)), + assigned_by: assigned_by.clone(), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("adm_role"), &event); + } + + /// Emit admin role deactivated event + pub fn emit_admin_role_deactivated( + env: &Env, + admin: &Address, + deactivated_by: &Address, + ) { + let event = AdminRoleEvent { + admin: admin.clone(), + role: String::from_str(env, "deactivated"), + assigned_by: deactivated_by.clone(), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("adm_deact"), &event); + } + + /// Emit market closed event + pub fn emit_market_closed( + env: &Env, + market_id: &Symbol, + admin: &Address, + ) { + let event = MarketClosedEvent { + market_id: market_id.clone(), + admin: admin.clone(), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("mkt_close"), &event); + } + + /// Emit market finalized event + pub fn emit_market_finalized( + env: &Env, + market_id: &Symbol, + admin: &Address, + outcome: &String, + ) { + let event = MarketFinalizedEvent { + market_id: market_id.clone(), + admin: admin.clone(), + outcome: outcome.clone(), + timestamp: env.ledger().timestamp(), + }; + + Self::store_event(env, &symbol_short!("mkt_final"), &event); + } + /// Store event in persistent storage fn store_event(env: &Env, event_key: &Symbol, event_data: &T) where From 8da0d43f53e840d61f9d6b348e43d7fb198c5ec8 Mon Sep 17 00:00:00 2001 From: 1nonlypiece Date: Mon, 7 Jul 2025 22:09:06 +0530 Subject: [PATCH 3/3] feat: Integrate admin management functionalities into Predictify Hybrid contract, streamlining admin operations such as initialization, fee configuration updates, market finalization, and contract configuration management, enhancing overall administrative efficiency and control --- contracts/predictify-hybrid/src/lib.rs | 107 +++++-------------------- 1 file changed, 22 insertions(+), 85 deletions(-) diff --git a/contracts/predictify-hybrid/src/lib.rs b/contracts/predictify-hybrid/src/lib.rs index b3d11f68..427a7f3a 100644 --- a/contracts/predictify-hybrid/src/lib.rs +++ b/contracts/predictify-hybrid/src/lib.rs @@ -52,6 +52,10 @@ use utils::{TimeUtils, StringUtils, NumericUtils, ValidationUtils, ConversionUti pub mod events; use events::{EventEmitter, EventLogger, EventValidator, EventHelpers, EventTestingUtils, EventDocumentation}; +// Admin management module +pub mod admin; +use admin::{AdminInitializer, AdminAccessControl, AdminFunctions, AdminRoleManager, AdminUtils}; + pub mod resolution; pub mod validation; @@ -72,9 +76,10 @@ pub struct PredictifyHybrid; #[contractimpl] impl PredictifyHybrid { pub fn initialize(env: Env, admin: Address) { - env.storage() - .persistent() - .set(&Symbol::new(&env, "Admin"), &admin); + match AdminInitializer::initialize(&env, &admin) { + Ok(_) => (), // Success + Err(e) => panic_with_error!(env, e), + } } // Create a market using the markets module @@ -147,7 +152,7 @@ impl PredictifyHybrid { // Update fee configuration (admin only) pub fn update_fee_config(env: Env, admin: Address, new_config: fees::FeeConfig) -> fees::FeeConfig { - match FeeManager::update_fee_config(&env, admin, new_config) { + match AdminFunctions::update_fee_config(&env, &admin, &new_config) { Ok(config) => config, Err(e) => panic_with_error!(env, e), } @@ -171,7 +176,7 @@ impl PredictifyHybrid { // Finalize market after disputes pub fn finalize_market(env: Env, admin: Address, market_id: Symbol, outcome: String) { - match resolution::MarketResolutionManager::finalize_market(&env, &admin, &market_id, &outcome) { + match AdminFunctions::finalize_market(&env, &admin, &market_id, &outcome) { Ok(_) => (), // Success Err(e) => panic_with_error!(env, e), } @@ -362,20 +367,10 @@ impl PredictifyHybrid { // Clean up market storage pub fn close_market(env: Env, admin: Address, market_id: Symbol) { - admin.require_auth(); - - // Verify admin - let stored_admin: Address = env - .storage() - .persistent() - .get(&Symbol::new(&env, "Admin")) - .expect("Admin not set"); - - // Use error helper for admin validation - errors::helpers::require_admin(&env, &admin, &stored_admin); - - // Remove market from storage - MarketStateManager::remove_market(&env, &market_id); + match AdminFunctions::close_market(&env, &admin, &market_id) { + Ok(_) => (), // Success + Err(e) => panic_with_error!(env, e), + } } // Helper function to create a market with Reflector oracle @@ -466,25 +461,7 @@ impl PredictifyHybrid { additional_days: u32, reason: String, ) { - admin.require_auth(); - - // Verify admin - let stored_admin: Address = env - .storage() - .persistent() - .get(&Symbol::new(&env, "Admin")) - .expect("Admin not set"); - - // Use error helper for admin validation - errors::helpers::require_admin(&env, &admin, &stored_admin); - - match ExtensionManager::extend_market_duration( - &env, - admin, - market_id, - additional_days, - reason, - ) { + match AdminFunctions::extend_market_duration(&env, &admin, &market_id, additional_days, &reason) { Ok(_) => (), // Success Err(e) => panic_with_error!(env, e), } @@ -747,22 +724,8 @@ impl PredictifyHybrid { /// Initialize contract with configuration pub fn initialize_with_config(env: Env, admin: Address, environment: Environment) { - // Set admin - env.storage() - .persistent() - .set(&Symbol::new(&env, "Admin"), &admin); - - // Initialize configuration based on environment - let config = match environment { - Environment::Development => ConfigManager::get_development_config(&env), - Environment::Testnet => ConfigManager::get_testnet_config(&env), - Environment::Mainnet => ConfigManager::get_mainnet_config(&env), - Environment::Custom => ConfigManager::get_development_config(&env), // Default to development for custom - }; - - // Store configuration - match ConfigManager::store_config(&env, &config) { - Ok(_) => (), + match AdminInitializer::initialize_with_config(&env, &admin, &environment) { + Ok(_) => (), // Success Err(e) => panic_with_error!(env, e), } } @@ -776,42 +739,16 @@ impl PredictifyHybrid { } /// Update contract configuration (admin only) - pub fn update_contract_config(env: Env, admin: Address, new_config: ContractConfig) -> ContractConfig { - // Verify admin permissions - let stored_admin: Address = env - .storage() - .persistent() - .get(&Symbol::new(&env, "Admin")) - .unwrap_or_else(|| panic!("Admin not set")); - - errors::helpers::require_admin(&env, &admin, &stored_admin); - - // Validate new configuration - match ConfigValidator::validate_contract_config(&new_config) { - Ok(_) => (), - Err(e) => panic_with_error!(env, e), - } - - // Store updated configuration - match ConfigManager::update_config(&env, &new_config) { - Ok(_) => new_config, - Err(e) => panic_with_error!(env, e), + pub fn update_contract_config(env: Env, admin: Address, new_config: ContractConfig) -> Result<(), Error> { + match AdminFunctions::update_contract_config(&env, &admin, &new_config) { + Ok(_) => Ok(()), + Err(e) => Err(e), } } /// Reset configuration to defaults pub fn reset_config_to_defaults(env: Env, admin: Address) -> ContractConfig { - // Verify admin permissions - let stored_admin: Address = env - .storage() - .persistent() - .get(&Symbol::new(&env, "Admin")) - .unwrap_or_else(|| panic!("Admin not set")); - - errors::helpers::require_admin(&env, &admin, &stored_admin); - - // Reset to defaults - match ConfigManager::reset_to_defaults(&env) { + match AdminFunctions::reset_config_to_defaults(&env, &admin) { Ok(config) => config, Err(e) => panic_with_error!(env, e), }