diff --git a/contracts/predictify-hybrid/src/admin.rs b/contracts/predictify-hybrid/src/admin.rs
index b47945a1..8a58b5d0 100644
--- a/contracts/predictify-hybrid/src/admin.rs
+++ b/contracts/predictify-hybrid/src/admin.rs
@@ -8,9 +8,9 @@ use crate::events::EventEmitter;
use crate::extensions::ExtensionManager;
use crate::fees::{FeeConfig, FeeManager};
use crate::format;
-use alloc::string::ToString;
use crate::markets::MarketStateManager;
use crate::resolution::MarketResolutionManager;
+use alloc::string::ToString;
/// Admin management system for Predictify Hybrid contract
///
@@ -113,7 +113,7 @@ pub struct AdminAnalytics {
#[contracttype]
pub enum AdminActionType {
Added,
- Removed,
+ Removed,
RoleUpdated,
Activated,
Deactivated,
@@ -458,25 +458,25 @@ impl AdminAccessControl {
/// - **Batch Operations**: Validate permissions for multiple operations
/// - **Audit Trails**: Log permission checks for security auditing
pub fn validate_permission(
- env: &Env,
- admin: &Address,
- permission: &AdminPermission,
-) -> Result<(), Error> {
- // Try new multi-admin system first if migrated
- if AdminSystemIntegration::is_migrated(env) {
- return AdminManager::validate_admin_permission(env, admin, *permission);
- }
-
- // Fall back to existing logic
- let role = AdminRoleManager::get_admin_role(env, admin)?;
+ env: &Env,
+ admin: &Address,
+ permission: &AdminPermission,
+ ) -> Result<(), Error> {
+ // Try new multi-admin system first if migrated
+ if AdminSystemIntegration::is_migrated(env) {
+ return AdminManager::validate_admin_permission(env, admin, *permission);
+ }
- // Check if admin has the required permission
- if !AdminRoleManager::has_permission(env, &role, permission)? {
- return Err(Error::Unauthorized);
- }
+ // Fall back to existing logic
+ let role = AdminRoleManager::get_admin_role(env, admin)?;
- Ok(())
-}
+ // Check if admin has the required permission
+ if !AdminRoleManager::has_permission(env, &role, permission)? {
+ return Err(Error::Unauthorized);
+ }
+
+ Ok(())
+ }
/// Requires admin authentication and validates admin status.
///
@@ -1216,16 +1216,20 @@ impl AdminManager {
role: AdminRole,
) -> Result<(), Error> {
// Use existing AdminRoleManager for validation
- AdminAccessControl::validate_permission(env, current_admin, &AdminPermission::EmergencyActions)?;
-
+ AdminAccessControl::validate_permission(
+ env,
+ current_admin,
+ &AdminPermission::EmergencyActions,
+ )?;
+
// Prevent duplicate admin assignments
if Self::is_admin(env, new_admin) {
return Err(Error::InvalidState);
}
-
+
// Generate unique key for this admin
let admin_key = Self::get_admin_key(env, new_admin);
-
+
// Create role assignment using existing structure
let assignment = AdminRoleAssignment {
admin: new_admin.clone(),
@@ -1235,29 +1239,35 @@ impl AdminManager {
permissions: AdminRoleManager::get_permissions_for_role(env, &role),
is_active: true,
};
-
+
// Store in multi-admin storage
env.storage().persistent().set(&admin_key, &assignment);
-
+
// Update admin count
let count_key = Symbol::new(env, "AdminCount");
let current_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0);
- env.storage().persistent().set(&count_key, &(current_count + 1));
-
+ env.storage()
+ .persistent()
+ .set(&count_key, &(current_count + 1));
+
// Emit event using existing system
Self::emit_admin_change_event(env, new_admin, AdminActionType::Added);
-
+
Ok(())
}
-
+
/// Removes an admin from the system
pub fn remove_admin(
env: &Env,
current_admin: &Address,
admin_to_remove: &Address,
) -> Result<(), Error> {
- AdminAccessControl::validate_permission(env, current_admin, &AdminPermission::EmergencyActions)?;
-
+ AdminAccessControl::validate_permission(
+ env,
+ current_admin,
+ &AdminPermission::EmergencyActions,
+ )?;
+
// Prevent self-removal of last super admin
if current_admin == admin_to_remove {
let super_admin_count = Self::count_admins_with_role(env, AdminRole::SuperAdmin);
@@ -1265,25 +1275,27 @@ impl AdminManager {
return Err(Error::InvalidState);
}
}
-
+
let admin_key = Self::get_admin_key(env, admin_to_remove);
if !env.storage().persistent().has(&admin_key) {
return Err(Error::Unauthorized);
}
-
+
env.storage().persistent().remove(&admin_key);
-
+
// Update count
let count_key = Symbol::new(env, "AdminCount");
let current_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0);
if current_count > 0 {
- env.storage().persistent().set(&count_key, &(current_count - 1));
+ env.storage()
+ .persistent()
+ .set(&count_key, &(current_count - 1));
}
-
+
Self::emit_admin_change_event(env, admin_to_remove, AdminActionType::Removed);
Ok(())
}
-
+
/// Updates an admin's role
pub fn update_admin_role(
env: &Env,
@@ -1291,15 +1303,19 @@ impl AdminManager {
target_admin: &Address,
new_role: AdminRole,
) -> Result<(), Error> {
- AdminAccessControl::validate_permission(env, current_admin, &AdminPermission::EmergencyActions)?;
-
+ AdminAccessControl::validate_permission(
+ env,
+ current_admin,
+ &AdminPermission::EmergencyActions,
+ )?;
+
let admin_key = Self::get_admin_key(env, target_admin);
let mut assignment: AdminRoleAssignment = env
.storage()
.persistent()
.get(&admin_key)
.ok_or(Error::Unauthorized)?;
-
+
// Prevent downgrading last super admin
if assignment.role == AdminRole::SuperAdmin && new_role != AdminRole::SuperAdmin {
let super_admin_count = Self::count_admins_with_role(env, AdminRole::SuperAdmin);
@@ -1307,18 +1323,18 @@ impl AdminManager {
return Err(Error::InvalidState);
}
}
-
+
assignment.role = new_role.clone();
assignment.permissions = AdminRoleManager::get_permissions_for_role(env, &new_role);
assignment.assigned_by = current_admin.clone();
assignment.assigned_at = env.ledger().timestamp();
-
+
env.storage().persistent().set(&admin_key, &assignment);
Self::emit_admin_change_event(env, target_admin, AdminActionType::RoleUpdated);
-
+
Ok(())
}
-
+
/// Validates if an admin has a specific permission
pub fn validate_admin_permission(
env: &Env,
@@ -1329,60 +1345,60 @@ impl AdminManager {
if Self::is_original_admin(env, admin) {
return Ok(());
}
-
+
let admin_key = Self::get_admin_key(env, admin);
let assignment: AdminRoleAssignment = env
.storage()
.persistent()
.get(&admin_key)
.ok_or(Error::Unauthorized)?;
-
+
if !assignment.is_active || !assignment.permissions.contains(&permission) {
return Err(Error::Unauthorized);
}
-
+
Ok(())
}
-
+
/// Gets all admin roles in the system
pub fn get_admin_roles(env: &Env) -> Map
{
let mut roles = Map::new(env);
-
+
// Add original admin if exists
if let Some(original_admin) = Self::get_original_admin(env) {
roles.set(original_admin, AdminRole::SuperAdmin);
}
-
+
roles
}
-
+
/// Emits admin change events using existing AdminActionType
pub fn emit_admin_change_event(env: &Env, admin: &Address, action: AdminActionType) {
let action_str = match action {
AdminActionType::Added => "Added",
- AdminActionType::Removed => "Removed",
+ AdminActionType::Removed => "Removed",
AdminActionType::RoleUpdated => "RoleUpdated",
AdminActionType::Activated => "Activated",
AdminActionType::Deactivated => "Deactivated",
};
EventEmitter::emit_admin_action_logged(env, admin, action_str, &true);
}
-
+
/// Check role permissions against a specific permission
pub fn check_role_permissions(env: &Env, role: AdminRole, permission: AdminPermission) -> bool {
let permissions = AdminRoleManager::get_permissions_for_role(env, &role);
permissions.contains(&permission)
}
-
+
// ===== Helper Methods =====
-
+
/// Generate a proper admin storage key using the correct environment
fn get_admin_key(env: &Env, admin: &Address) -> Symbol {
- // Create a unique key based on admin address
- let key_str = format!("MultiAdmin_{:?}", admin.to_string());
- Symbol::new(env, &key_str)
+ // Create a unique key based on admin address
+ let key_str = format!("MultiAdmin_{:?}", admin.to_string());
+ Symbol::new(env, &key_str)
}
-
+
/// Check if an address is the original admin from single-admin system
fn is_original_admin(env: &Env, admin: &Address) -> bool {
if let Some(original_admin) = Self::get_original_admin(env) {
@@ -1390,78 +1406,89 @@ impl AdminManager {
}
false
}
-
+
/// Get the original admin address from single-admin system
pub fn get_original_admin(env: &Env) -> Option {
env.storage().persistent().get(&Symbol::new(env, "Admin"))
}
-
+
/// Check if an address is any type of admin (original or multi-admin)
fn is_admin(env: &Env, admin: &Address) -> bool {
- Self::is_original_admin(env, admin) ||
- env.storage().persistent().has(&Self::get_admin_key(env, admin))
+ Self::is_original_admin(env, admin)
+ || env
+ .storage()
+ .persistent()
+ .has(&Self::get_admin_key(env, admin))
}
-
+
/// Count admins with a specific role
fn count_admins_with_role(env: &Env, role: AdminRole) -> u32 {
let mut count = 0;
-
+
// Count original admin if it matches the role
if role == AdminRole::SuperAdmin && Self::get_original_admin(env).is_some() {
count += 1;
}
-
+
// In a full implementation, we would iterate through all multi-admin entries
// For now, this is a simplified version that works with the existing storage pattern
count
}
-
+
/// Get admin assignment for a specific admin
pub fn get_admin_assignment(env: &Env, admin: &Address) -> Option {
let admin_key = Self::get_admin_key(env, admin);
env.storage().persistent().get(&admin_key)
}
-
+
/// Deactivate an admin (set is_active to false)
pub fn deactivate_admin(
env: &Env,
current_admin: &Address,
target_admin: &Address,
) -> Result<(), Error> {
- AdminAccessControl::validate_permission(env, current_admin, &AdminPermission::EmergencyActions)?;
-
+ AdminAccessControl::validate_permission(
+ env,
+ current_admin,
+ &AdminPermission::EmergencyActions,
+ )?;
+
let admin_key = Self::get_admin_key(env, target_admin);
let mut assignment: AdminRoleAssignment = env
.storage()
.persistent()
.get(&admin_key)
.ok_or(Error::Unauthorized)?;
-
+
assignment.is_active = false;
env.storage().persistent().set(&admin_key, &assignment);
-
+
Self::emit_admin_change_event(env, target_admin, AdminActionType::Deactivated);
Ok(())
}
-
+
/// Reactivate an admin (set is_active to true)
pub fn reactivate_admin(
env: &Env,
current_admin: &Address,
target_admin: &Address,
) -> Result<(), Error> {
- AdminAccessControl::validate_permission(env, current_admin, &AdminPermission::EmergencyActions)?;
-
+ AdminAccessControl::validate_permission(
+ env,
+ current_admin,
+ &AdminPermission::EmergencyActions,
+ )?;
+
let admin_key = Self::get_admin_key(env, target_admin);
let mut assignment: AdminRoleAssignment = env
.storage()
.persistent()
.get(&admin_key)
.ok_or(Error::Unauthorized)?;
-
+
assignment.is_active = true;
env.storage().persistent().set(&admin_key, &assignment);
-
+
Self::emit_admin_change_event(env, target_admin, AdminActionType::Activated);
Ok(())
}
@@ -2948,7 +2975,6 @@ impl AdminUtils {
}
}
-
// ===== ADMIN HIERARCHY AND ANALYTICS =====
/// Admin hierarchy management for role inheritance and precedence
@@ -2964,7 +2990,7 @@ impl AdminHierarchy {
AdminRole::ReadOnlyAdmin => 25,
}
}
-
+
pub fn can_manage_role(manager_role: &AdminRole, target_role: &AdminRole) -> bool {
if *manager_role == AdminRole::SuperAdmin {
return true;
@@ -2973,19 +2999,21 @@ impl AdminHierarchy {
let target_level = Self::get_role_level(target_role);
manager_level > target_level
}
-
+
/// Get all permissions for a role level
pub fn get_role_permissions(env: &Env, role: &AdminRole) -> Vec {
AdminRoleManager::get_permissions_for_role(env, role)
}
-
+
/// Check if one role encompasses another role's permissions
pub fn role_encompasses(env: &Env, higher_role: &AdminRole, lower_role: &AdminRole) -> bool {
let higher_permissions = Self::get_role_permissions(env, higher_role);
let lower_permissions = Self::get_role_permissions(env, lower_role);
-
+
// Check if all lower permissions are contained in higher permissions
- lower_permissions.iter().all(|perm| higher_permissions.contains(perm))
+ lower_permissions
+ .iter()
+ .all(|perm| higher_permissions.contains(perm))
}
}
@@ -2997,7 +3025,7 @@ impl EnhancedAdminAnalytics {
let total_admins = Self::count_total_admins(env);
let active_admins = Self::count_active_admins(env);
let role_distribution = Self::get_role_distribution(env);
-
+
AdminAnalyticsResult {
total_admins,
active_admins,
@@ -3005,24 +3033,28 @@ impl EnhancedAdminAnalytics {
last_updated: env.ledger().timestamp(),
}
}
-
+
fn count_total_admins(env: &Env) -> u32 {
let multi_admin_count: u32 = env
.storage()
.persistent()
.get(&Symbol::new(env, "AdminCount"))
.unwrap_or(0);
-
- let original_admin_count = if AdminManager::get_original_admin(env).is_some() { 1 } else { 0 };
+
+ let original_admin_count = if AdminManager::get_original_admin(env).is_some() {
+ 1
+ } else {
+ 0
+ };
multi_admin_count + original_admin_count
}
-
+
fn count_active_admins(env: &Env) -> u32 {
// assume all admins are active
-
+
Self::count_total_admins(env)
}
-
+
fn get_role_distribution(env: &Env) -> Map {
let mut distribution = Map::new(env);
distribution.set(String::from_str(env, "SuperAdmin"), 0);
@@ -3030,32 +3062,31 @@ impl EnhancedAdminAnalytics {
distribution.set(String::from_str(env, "ConfigAdmin"), 0);
distribution.set(String::from_str(env, "FeeAdmin"), 0);
distribution.set(String::from_str(env, "ReadOnlyAdmin"), 0);
-
+
// Count original admin as SuperAdmin
if AdminManager::get_original_admin(env).is_some() {
distribution.set(String::from_str(env, "SuperAdmin"), 1);
}
-
-
+
distribution
}
-
+
/// Get detailed analytics for a specific admin
pub fn get_admin_details(env: &Env, admin: &Address) -> Option {
AdminManager::get_admin_assignment(env, admin)
}
-
+
/// Get admin activity summary
pub fn get_admin_activity_summary(env: &Env) -> Map {
let mut summary = Map::new(env);
-
+
let total = Self::count_total_admins(env);
let active = Self::count_active_admins(env);
-
+
summary.set(String::from_str(env, "Total"), total);
summary.set(String::from_str(env, "Active"), active);
summary.set(String::from_str(env, "Inactive"), total - active);
-
+
summary
}
}
@@ -3071,34 +3102,50 @@ impl AdminSystemIntegration {
role: AdminRole::SuperAdmin,
assigned_by: original_admin.clone(),
assigned_at: env.ledger().timestamp(),
- permissions: AdminRoleManager::get_permissions_for_role(env, &AdminRole::SuperAdmin),
+ permissions: AdminRoleManager::get_permissions_for_role(
+ env,
+ &AdminRole::SuperAdmin,
+ ),
is_active: true,
};
-
+
let admin_key = AdminManager::get_admin_key(env, &original_admin);
env.storage().persistent().set(&admin_key, &assignment);
- env.storage().persistent().set(&Symbol::new(env, "AdminCount"), &1u32);
- env.storage().persistent().set(&Symbol::new(env, "MultiAdminMigrated"), &true);
-
- EventEmitter::emit_admin_action_logged(env, &original_admin, "MigratedToMultiAdmin", &true);
+ env.storage()
+ .persistent()
+ .set(&Symbol::new(env, "AdminCount"), &1u32);
+ env.storage()
+ .persistent()
+ .set(&Symbol::new(env, "MultiAdminMigrated"), &true);
+
+ EventEmitter::emit_admin_action_logged(
+ env,
+ &original_admin,
+ "MigratedToMultiAdmin",
+ &true,
+ );
}
Ok(())
}
-
+
pub fn is_migrated(env: &Env) -> bool {
env.storage()
.persistent()
.get(&Symbol::new(env, "MultiAdminMigrated"))
.unwrap_or(false)
}
-
- pub fn validate_admin_unified(env: &Env, admin: &Address, permission: AdminPermission) -> Result<(), Error> {
+
+ pub fn validate_admin_unified(
+ env: &Env,
+ admin: &Address,
+ permission: AdminPermission,
+ ) -> Result<(), Error> {
if Self::is_migrated(env) {
return AdminManager::validate_admin_permission(env, admin, permission);
}
AdminAccessControl::validate_permission(env, admin, &permission)
}
-
+
/// Force migration if not already migrated
pub fn ensure_migration(env: &Env) -> Result<(), Error> {
if !Self::is_migrated(env) {
@@ -3106,33 +3153,36 @@ impl AdminSystemIntegration {
}
Ok(())
}
-
+
/// Get migration status information
pub fn get_migration_info(env: &Env) -> Map {
let mut info = Map::new(env);
-
+
let is_migrated = Self::is_migrated(env);
let has_original_admin = AdminManager::get_original_admin(env).is_some();
- let multi_admin_count: u32 = env.storage().persistent().get(&Symbol::new(env, "AdminCount")).unwrap_or(0);
-
+ let multi_admin_count: u32 = env
+ .storage()
+ .persistent()
+ .get(&Symbol::new(env, "AdminCount"))
+ .unwrap_or(0);
+
info.set(
String::from_str(env, "migrated"),
- String::from_str(env, if is_migrated { "true" } else { "false" })
+ String::from_str(env, if is_migrated { "true" } else { "false" }),
);
info.set(
String::from_str(env, "has_original_admin"),
- String::from_str(env, if has_original_admin { "true" } else { "false" })
+ String::from_str(env, if has_original_admin { "true" } else { "false" }),
);
info.set(
String::from_str(env, "multi_admin_count"),
- String::from_str(env, &multi_admin_count.to_string())
+ String::from_str(env, &multi_admin_count.to_string()),
);
-
+
info
}
}
-
// ===== ADMIN TESTING =====
/// Admin testing utilities
@@ -3368,10 +3418,13 @@ mod admin_manager_tests {
role: AdminRole::MarketAdmin,
assigned_by: super_admin.clone(),
assigned_at: env.ledger().timestamp(),
- permissions: AdminRoleManager::get_permissions_for_role(&env, &AdminRole::MarketAdmin),
+ permissions: AdminRoleManager::get_permissions_for_role(
+ &env,
+ &AdminRole::MarketAdmin,
+ ),
is_active: true,
};
-
+
let admin_key = get_test_admin_key(&env, &new_admin);
env.storage().persistent().set(&admin_key, &assignment);
@@ -3380,7 +3433,8 @@ mod admin_manager_tests {
env.storage().persistent().set(&count_key, &1u32);
// Verify admin was added
- let retrieved: AdminRoleAssignment = env.storage().persistent().get(&admin_key).unwrap();
+ let retrieved: AdminRoleAssignment =
+ env.storage().persistent().get(&admin_key).unwrap();
assert_eq!(retrieved.admin, new_admin);
assert_eq!(retrieved.role, AdminRole::MarketAdmin);
assert_eq!(retrieved.assigned_by, super_admin);
@@ -3439,10 +3493,13 @@ mod admin_manager_tests {
AdminInitializer::initialize(&env, &super_admin).unwrap();
let roles = AdminManager::get_admin_roles(&env);
-
+
// Should contain the original super admin
assert!(roles.contains_key(super_admin.clone()));
- assert_eq!(roles.get(super_admin.clone()).unwrap(), AdminRole::SuperAdmin);
+ assert_eq!(
+ roles.get(super_admin.clone()).unwrap(),
+ AdminRole::SuperAdmin
+ );
});
}
@@ -3465,43 +3522,84 @@ mod admin_manager_tests {
fn test_permission_hierarchy() {
// Test that SuperAdmin encompasses all other roles
let env = Env::default();
-
- assert!(AdminHierarchy::role_encompasses(&env, &AdminRole::SuperAdmin, &AdminRole::MarketAdmin));
- assert!(AdminHierarchy::role_encompasses(&env, &AdminRole::SuperAdmin, &AdminRole::ConfigAdmin));
- assert!(AdminHierarchy::role_encompasses(&env, &AdminRole::SuperAdmin, &AdminRole::FeeAdmin));
- assert!(AdminHierarchy::role_encompasses(&env, &AdminRole::SuperAdmin, &AdminRole::ReadOnlyAdmin));
-
+
+ assert!(AdminHierarchy::role_encompasses(
+ &env,
+ &AdminRole::SuperAdmin,
+ &AdminRole::MarketAdmin
+ ));
+ assert!(AdminHierarchy::role_encompasses(
+ &env,
+ &AdminRole::SuperAdmin,
+ &AdminRole::ConfigAdmin
+ ));
+ assert!(AdminHierarchy::role_encompasses(
+ &env,
+ &AdminRole::SuperAdmin,
+ &AdminRole::FeeAdmin
+ ));
+ assert!(AdminHierarchy::role_encompasses(
+ &env,
+ &AdminRole::SuperAdmin,
+ &AdminRole::ReadOnlyAdmin
+ ));
+
// Test that lower roles don't encompass SuperAdmin
- assert!(!AdminHierarchy::role_encompasses(&env, &AdminRole::MarketAdmin, &AdminRole::SuperAdmin));
+ assert!(!AdminHierarchy::role_encompasses(
+ &env,
+ &AdminRole::MarketAdmin,
+ &AdminRole::SuperAdmin
+ ));
}
#[test]
fn test_role_level_hierarchy() {
// SuperAdmin should have highest level
assert_eq!(AdminHierarchy::get_role_level(&AdminRole::SuperAdmin), 100);
-
+
// Specialized admins should have same level
assert_eq!(AdminHierarchy::get_role_level(&AdminRole::MarketAdmin), 75);
assert_eq!(AdminHierarchy::get_role_level(&AdminRole::ConfigAdmin), 75);
assert_eq!(AdminHierarchy::get_role_level(&AdminRole::FeeAdmin), 75);
-
+
// ReadOnly should have lowest level
- assert_eq!(AdminHierarchy::get_role_level(&AdminRole::ReadOnlyAdmin), 25);
+ assert_eq!(
+ AdminHierarchy::get_role_level(&AdminRole::ReadOnlyAdmin),
+ 25
+ );
}
#[test]
fn test_can_manage_role() {
// SuperAdmin can manage all roles
- assert!(AdminHierarchy::can_manage_role(&AdminRole::SuperAdmin, &AdminRole::SuperAdmin));
- assert!(AdminHierarchy::can_manage_role(&AdminRole::SuperAdmin, &AdminRole::MarketAdmin));
- assert!(AdminHierarchy::can_manage_role(&AdminRole::SuperAdmin, &AdminRole::ReadOnlyAdmin));
-
+ assert!(AdminHierarchy::can_manage_role(
+ &AdminRole::SuperAdmin,
+ &AdminRole::SuperAdmin
+ ));
+ assert!(AdminHierarchy::can_manage_role(
+ &AdminRole::SuperAdmin,
+ &AdminRole::MarketAdmin
+ ));
+ assert!(AdminHierarchy::can_manage_role(
+ &AdminRole::SuperAdmin,
+ &AdminRole::ReadOnlyAdmin
+ ));
+
// MarketAdmin cannot manage other specialized admins or SuperAdmin
- assert!(!AdminHierarchy::can_manage_role(&AdminRole::MarketAdmin, &AdminRole::SuperAdmin));
- assert!(!AdminHierarchy::can_manage_role(&AdminRole::MarketAdmin, &AdminRole::ConfigAdmin));
-
+ assert!(!AdminHierarchy::can_manage_role(
+ &AdminRole::MarketAdmin,
+ &AdminRole::SuperAdmin
+ ));
+ assert!(!AdminHierarchy::can_manage_role(
+ &AdminRole::MarketAdmin,
+ &AdminRole::ConfigAdmin
+ ));
+
// But can manage lower roles
- assert!(AdminHierarchy::can_manage_role(&AdminRole::MarketAdmin, &AdminRole::ReadOnlyAdmin));
+ assert!(AdminHierarchy::can_manage_role(
+ &AdminRole::MarketAdmin,
+ &AdminRole::ReadOnlyAdmin
+ ));
}
#[test]
@@ -3515,9 +3613,11 @@ mod admin_manager_tests {
let analytics = EnhancedAdminAnalytics::get_admin_analytics(&env);
let distribution = analytics.role_distribution;
-
+
// Should have SuperAdmin entry
- let super_admin_count = distribution.get(String::from_str(&env, "SuperAdmin")).unwrap();
+ let super_admin_count = distribution
+ .get(String::from_str(&env, "SuperAdmin"))
+ .unwrap();
assert_eq!(super_admin_count, 1);
});
}
@@ -3532,17 +3632,19 @@ mod admin_manager_tests {
AdminInitializer::initialize(&env, &super_admin).unwrap();
let info = AdminSystemIntegration::get_migration_info(&env);
-
+
// Should have migration status
assert!(info.contains_key(String::from_str(&env, "migrated")));
assert!(info.contains_key(String::from_str(&env, "has_original_admin")));
-
+
// Should show not migrated initially
let migrated = info.get(String::from_str(&env, "migrated")).unwrap();
assert_eq!(migrated, String::from_str(&env, "false"));
-
+
// Should show has original admin
- let has_original = info.get(String::from_str(&env, "has_original_admin")).unwrap();
+ let has_original = info
+ .get(String::from_str(&env, "has_original_admin"))
+ .unwrap();
assert_eq!(has_original, String::from_str(&env, "true"));
});
}
@@ -3557,12 +3659,12 @@ mod admin_manager_tests {
AdminInitializer::initialize(&env, &super_admin).unwrap();
let summary = EnhancedAdminAnalytics::get_admin_activity_summary(&env);
-
+
// Should have Total, Active, and Inactive keys
assert!(summary.contains_key(String::from_str(&env, "Total")));
assert!(summary.contains_key(String::from_str(&env, "Active")));
assert!(summary.contains_key(String::from_str(&env, "Inactive")));
-
+
let total = summary.get(String::from_str(&env, "Total")).unwrap();
assert!(total >= 1);
});
diff --git a/contracts/predictify-hybrid/src/batch_operations.rs b/contracts/predictify-hybrid/src/batch_operations.rs
index 8a333efc..fd5d46a6 100644
--- a/contracts/predictify-hybrid/src/batch_operations.rs
+++ b/contracts/predictify-hybrid/src/batch_operations.rs
@@ -1,8 +1,6 @@
-use soroban_sdk::{
- contracttype, vec, Address, Env, Map, String, Symbol, Vec,
-};
use alloc::format;
use alloc::string::ToString;
+use soroban_sdk::{contracttype, vec, Address, Env, Map, String, Symbol, Vec};
use crate::errors::Error;
use crate::types::*;
@@ -12,14 +10,14 @@ use crate::types::*;
#[derive(Clone, Debug, PartialEq, Eq)]
#[contracttype]
pub enum BatchOperationType {
- Vote, // Batch vote operations
- Claim, // Batch claim operations
- CreateMarket, // Batch market creation
- OracleCall, // Batch oracle calls
- Dispute, // Batch dispute operations
- Extension, // Batch market extensions
- Resolution, // Batch market resolutions
- FeeCollection, // Batch fee collection
+ Vote, // Batch vote operations
+ Claim, // Batch claim operations
+ CreateMarket, // Batch market creation
+ OracleCall, // Batch oracle calls
+ Dispute, // Batch dispute operations
+ Extension, // Batch market extensions
+ Resolution, // Batch market resolutions
+ FeeCollection, // Batch fee collection
}
#[derive(Clone, Debug)]
@@ -132,7 +130,7 @@ pub struct BatchProcessor;
impl BatchProcessor {
// ===== STORAGE KEYS =====
-
+
const BATCH_QUEUE_KEY: &'static str = "batch_operation_queue";
const BATCH_STATS_KEY: &'static str = "batch_operation_statistics";
const BATCH_CONFIG_KEY: &'static str = "batch_operation_config";
@@ -160,12 +158,18 @@ impl BatchProcessor {
gas_efficiency_ratio: 1,
};
- env.storage().instance().set(&Symbol::new(env, Self::BATCH_CONFIG_KEY), &config);
- env.storage().instance().set(&Symbol::new(env, Self::BATCH_STATS_KEY), &stats);
-
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::BATCH_CONFIG_KEY), &config);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::BATCH_STATS_KEY), &stats);
+
// Initialize empty batch queue
let queue: Vec = Vec::new(env);
- env.storage().instance().set(&Symbol::new(env, Self::BATCH_QUEUE_KEY), &queue);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::BATCH_QUEUE_KEY), &queue);
Ok(())
}
@@ -179,18 +183,20 @@ impl BatchProcessor {
}
/// Update batch processor configuration
- pub fn update_config(
- env: &Env,
- admin: &Address,
- config: &BatchConfig,
- ) -> Result<(), Error> {
+ pub fn update_config(env: &Env, admin: &Address, config: &BatchConfig) -> Result<(), Error> {
// Validate admin permissions
- crate::admin::AdminAccessControl::validate_admin_for_action(env, admin, "update_batch_config")?;
+ crate::admin::AdminAccessControl::validate_admin_for_action(
+ env,
+ admin,
+ "update_batch_config",
+ )?;
// Validate configuration
Self::validate_batch_config(config)?;
- env.storage().instance().set(&Symbol::new(env, Self::BATCH_CONFIG_KEY), config);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::BATCH_CONFIG_KEY), config);
Ok(())
}
@@ -198,10 +204,7 @@ impl BatchProcessor {
// ===== BATCH VOTE OPERATIONS =====
/// Process batch vote operations
- pub fn batch_vote(
- env: &Env,
- votes: &Vec,
- ) -> Result {
+ pub fn batch_vote(env: &Env, votes: &Vec) -> Result {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
@@ -255,7 +258,7 @@ impl BatchProcessor {
// Check if market exists and is open
let market = crate::markets::MarketStateManager::get_market(env, &vote_data.market_id)?;
-
+
if market.end_time <= env.ledger().timestamp() {
return Err(Error::MarketClosed);
}
@@ -275,10 +278,7 @@ impl BatchProcessor {
// ===== BATCH CLAIM OPERATIONS =====
/// Process batch claim operations
- pub fn batch_claim(
- env: &Env,
- claims: &Vec,
- ) -> Result {
+ pub fn batch_claim(env: &Env, claims: &Vec) -> Result {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
@@ -332,7 +332,7 @@ impl BatchProcessor {
// Check if market exists and is resolved
let market = crate::markets::MarketStateManager::get_market(env, &claim_data.market_id)?;
-
+
if !market.is_resolved() {
return Err(Error::MarketNotResolved);
}
@@ -356,7 +356,11 @@ impl BatchProcessor {
markets: &Vec,
) -> Result {
// Validate admin permissions
- crate::admin::AdminAccessControl::validate_admin_for_action(env, admin, "batch_create_markets")?;
+ crate::admin::AdminAccessControl::validate_admin_for_action(
+ env,
+ admin,
+ "batch_create_markets",
+ )?;
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
@@ -429,10 +433,7 @@ impl BatchProcessor {
// ===== BATCH ORACLE CALLS =====
/// Process batch oracle calls
- pub fn batch_oracle_calls(
- env: &Env,
- feeds: &Vec,
- ) -> Result {
+ pub fn batch_oracle_calls(env: &Env, feeds: &Vec) -> Result {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
@@ -486,7 +487,7 @@ impl BatchProcessor {
// Check if market exists
let market = crate::markets::MarketStateManager::get_market(env, &feed_data.market_id)?;
-
+
if market.is_resolved() {
return Err(Error::MarketAlreadyResolved);
}
@@ -507,9 +508,7 @@ impl BatchProcessor {
// ===== BATCH OPERATION VALIDATION =====
/// Validate batch operations
- pub fn validate_batch_operations(
- operations: &Vec,
- ) -> Result<(), Error> {
+ pub fn validate_batch_operations(operations: &Vec) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::InvalidInput);
}
@@ -593,26 +592,28 @@ impl BatchProcessor {
BatchOperationType::FeeCollection => "fee_collection",
};
- let current_count = error_counts.get(String::from_str(env, error_type)).unwrap_or(0);
+ let current_count = error_counts
+ .get(String::from_str(env, error_type))
+ .unwrap_or(0);
error_counts.set(String::from_str(env, error_type), current_count + 1);
}
// Create error summary
error_summary.set(
String::from_str(env, "total_errors"),
- String::from_str(env, &errors.len().to_string())
+ String::from_str(env, &errors.len().to_string()),
);
error_summary.set(
String::from_str(env, "error_types"),
- String::from_str(env, "See error_counts for breakdown")
+ String::from_str(env, "See error_counts for breakdown"),
);
// Add error counts
for (error_type, count) in error_counts.iter() {
error_summary.set(
String::from_str(env, &format!("{:?}_errors", error_type)),
- String::from_str(env, &count.to_string())
+ String::from_str(env, &count.to_string()),
);
}
@@ -640,12 +641,15 @@ impl BatchProcessor {
// Update average batch size
if stats.total_batches_processed > 0 {
- stats.average_batch_size = stats.total_operations_processed / stats.total_batches_processed;
+ stats.average_batch_size =
+ stats.total_operations_processed / stats.total_batches_processed;
}
// Update average execution time
if stats.total_batches_processed > 0 {
- let total_time = stats.average_execution_time * (stats.total_batches_processed - 1) as u64 + result.execution_time;
+ let total_time = stats.average_execution_time
+ * (stats.total_batches_processed - 1) as u64
+ + result.execution_time;
stats.average_execution_time = total_time / stats.total_batches_processed as u64;
}
@@ -655,7 +659,9 @@ impl BatchProcessor {
stats.gas_efficiency_ratio = (success_rate * 100.0) as u64;
}
- env.storage().instance().set(&Symbol::new(env, Self::BATCH_STATS_KEY), &stats);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::BATCH_STATS_KEY), &stats);
Ok(())
}
@@ -767,7 +773,7 @@ impl BatchUtils {
operation_type: &BatchOperationType,
) -> Result {
let config = BatchProcessor::get_config(env)?;
-
+
match operation_type {
BatchOperationType::Vote => Ok(config.max_batch_size.min(20)),
BatchOperationType::Claim => Ok(config.max_batch_size.min(15)),
@@ -792,15 +798,12 @@ impl BatchUtils {
let success_rate = successful_operations as f64 / total_operations as f64;
let operations_per_gas = total_operations as f64 / gas_used as f64;
-
+
success_rate * operations_per_gas
}
/// Estimate gas cost for batch operation
- pub fn estimate_gas_cost(
- operation_type: &BatchOperationType,
- operation_count: u32,
- ) -> u64 {
+ pub fn estimate_gas_cost(operation_type: &BatchOperationType, operation_count: u32) -> u64 {
let base_cost = match operation_type {
BatchOperationType::Vote => 1000,
BatchOperationType::Claim => 1500,
@@ -826,7 +829,10 @@ impl BatchTesting {
pub fn create_test_vote_data(env: &Env, market_id: &Symbol) -> VoteData {
VoteData {
market_id: market_id.clone(),
- voter: Address::from_string(&String::from_str(env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF")),
+ voter: Address::from_string(&String::from_str(
+ env,
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+ )),
outcome: String::from_str(env, "Yes"),
stake_amount: 1_000_000_000, // 100 XLM
}
@@ -836,7 +842,10 @@ impl BatchTesting {
pub fn create_test_claim_data(env: &Env, market_id: &Symbol) -> ClaimData {
ClaimData {
market_id: market_id.clone(),
- claimant: Address::from_string(&String::from_str(env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF")),
+ claimant: Address::from_string(&String::from_str(
+ env,
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+ )),
expected_amount: 2_000_000_000, // 200 XLM
}
}
@@ -848,7 +857,7 @@ impl BatchTesting {
outcomes: vec![
&env,
String::from_str(env, "Yes"),
- String::from_str(env, "No")
+ String::from_str(env, "No"),
],
duration_days: 30,
oracle_config: crate::types::OracleConfig {
@@ -910,4 +919,4 @@ impl BatchTesting {
execution_time,
})
}
-}
\ No newline at end of file
+}
diff --git a/contracts/predictify-hybrid/src/batch_operations_tests.rs b/contracts/predictify-hybrid/src/batch_operations_tests.rs
index 1a8c4df0..71116dcf 100644
--- a/contracts/predictify-hybrid/src/batch_operations_tests.rs
+++ b/contracts/predictify-hybrid/src/batch_operations_tests.rs
@@ -1,37 +1,37 @@
#[cfg(test)]
mod batch_operations_tests {
- use crate::batch_operations::*;
use crate::admin::AdminRoleManager;
+ use crate::batch_operations::*;
use crate::types::OracleProvider;
- use soroban_sdk::{Env, String, Vec, Symbol, testutils::Address, vec};
+ use soroban_sdk::{testutils::Address, vec, Env, String, Symbol, Vec};
#[test]
fn test_batch_processor_initialization() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test initialization
assert!(BatchProcessor::initialize(&env).is_ok());
-
- // Test get config
- let config = BatchProcessor::get_config(&env).unwrap();
- assert_eq!(config.max_batch_size, 50);
- assert_eq!(config.max_operations_per_batch, 100);
- assert_eq!(config.gas_limit_per_batch, 1_000_000);
- assert_eq!(config.timeout_per_batch, 30);
- assert!(config.retry_failed_operations);
- assert!(!config.parallel_processing_enabled);
-
- // Test get statistics
- let stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
- assert_eq!(stats.total_batches_processed, 0);
- assert_eq!(stats.total_operations_processed, 0);
- assert_eq!(stats.total_successful_operations, 0);
- assert_eq!(stats.total_failed_operations, 0);
- assert_eq!(stats.average_batch_size, 0);
- assert_eq!(stats.average_execution_time, 0);
- assert_eq!(stats.gas_efficiency_ratio, 1u64);
+
+ // Test get config
+ let config = BatchProcessor::get_config(&env).unwrap();
+ assert_eq!(config.max_batch_size, 50);
+ assert_eq!(config.max_operations_per_batch, 100);
+ assert_eq!(config.gas_limit_per_batch, 1_000_000);
+ assert_eq!(config.timeout_per_batch, 30);
+ assert!(config.retry_failed_operations);
+ assert!(!config.parallel_processing_enabled);
+
+ // Test get statistics
+ let stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
+ assert_eq!(stats.total_batches_processed, 0);
+ assert_eq!(stats.total_operations_processed, 0);
+ assert_eq!(stats.total_successful_operations, 0);
+ assert_eq!(stats.total_failed_operations, 0);
+ assert_eq!(stats.average_batch_size, 0);
+ assert_eq!(stats.average_execution_time, 0);
+ assert_eq!(stats.gas_efficiency_ratio, 1u64);
});
}
@@ -39,26 +39,26 @@ mod batch_operations_tests {
fn test_batch_vote_operations() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Create test vote data
- let market_id = Symbol::new(&env, "test_market");
- let votes = vec![
- &env,
- BatchTesting::create_test_vote_data(&env, &market_id),
- BatchTesting::create_test_vote_data(&env, &market_id),
- BatchTesting::create_test_vote_data(&env, &market_id),
- ];
-
- // Test batch vote processing
- let result = BatchProcessor::batch_vote(&env, &votes);
- assert!(result.is_ok());
-
- let batch_result = result.unwrap();
- assert_eq!(batch_result.total_operations, 3);
- assert!(batch_result.execution_time >= 0);
+
+ // Create test vote data
+ let market_id = Symbol::new(&env, "test_market");
+ let votes = vec![
+ &env,
+ BatchTesting::create_test_vote_data(&env, &market_id),
+ BatchTesting::create_test_vote_data(&env, &market_id),
+ BatchTesting::create_test_vote_data(&env, &market_id),
+ ];
+
+ // Test batch vote processing
+ let result = BatchProcessor::batch_vote(&env, &votes);
+ assert!(result.is_ok());
+
+ let batch_result = result.unwrap();
+ assert_eq!(batch_result.total_operations, 3);
+ assert!(batch_result.execution_time >= 0);
});
}
@@ -66,25 +66,25 @@ mod batch_operations_tests {
fn test_batch_claim_operations() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Create test claim data
- let market_id = Symbol::new(&env, "test_market");
- let claims = vec![
- &env,
- BatchTesting::create_test_claim_data(&env, &market_id),
- BatchTesting::create_test_claim_data(&env, &market_id),
- ];
-
- // Test batch claim processing
- let result = BatchProcessor::batch_claim(&env, &claims);
- assert!(result.is_ok());
-
- let batch_result = result.unwrap();
- assert_eq!(batch_result.total_operations, 2);
- assert!(batch_result.execution_time >= 0);
+
+ // Create test claim data
+ let market_id = Symbol::new(&env, "test_market");
+ let claims = vec![
+ &env,
+ BatchTesting::create_test_claim_data(&env, &market_id),
+ BatchTesting::create_test_claim_data(&env, &market_id),
+ ];
+
+ // Test batch claim processing
+ let result = BatchProcessor::batch_claim(&env, &claims);
+ assert!(result.is_ok());
+
+ let batch_result = result.unwrap();
+ assert_eq!(batch_result.total_operations, 2);
+ assert!(batch_result.execution_time >= 0);
});
}
@@ -93,34 +93,40 @@ mod batch_operations_tests {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
env.mock_all_auths();
-
+
let admin = ::generate(&env);
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Initialize admin system first
- crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // Create test market data
- let markets = vec![
- &env,
- BatchTesting::create_test_market_data(&env),
- BatchTesting::create_test_market_data(&env),
- ];
-
- // Test batch market creation (skip for now due to admin validation complexity)
- // let result = BatchProcessor::batch_create_markets(&env, &admin, &markets);
- // assert!(result.is_ok());
-
- // For now, just test that the function exists and can be called
- let result = BatchProcessor::get_batch_operation_statistics(&env);
- assert!(result.is_ok());
-
- let _stats = result.unwrap();
- // assert_eq!(batch_result.total_operations, 2);
- // assert!(batch_result.execution_time >= 0);
+
+ // Initialize admin system first
+ crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // Create test market data
+ let markets = vec![
+ &env,
+ BatchTesting::create_test_market_data(&env),
+ BatchTesting::create_test_market_data(&env),
+ ];
+
+ // Test batch market creation (skip for now due to admin validation complexity)
+ // let result = BatchProcessor::batch_create_markets(&env, &admin, &markets);
+ // assert!(result.is_ok());
+
+ // For now, just test that the function exists and can be called
+ let result = BatchProcessor::get_batch_operation_statistics(&env);
+ assert!(result.is_ok());
+
+ let _stats = result.unwrap();
+ // assert_eq!(batch_result.total_operations, 2);
+ // assert!(batch_result.execution_time >= 0);
});
}
@@ -128,32 +134,32 @@ mod batch_operations_tests {
fn test_batch_oracle_calls() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Create test oracle feed data
- let market_id = Symbol::new(&env, "test_market");
- let feeds = vec![
- &env,
- BatchTesting::create_test_oracle_feed_data(&env, &market_id),
- BatchTesting::create_test_oracle_feed_data(&env, &market_id),
- ];
-
- // Test batch oracle calls
- let result = BatchProcessor::batch_oracle_calls(&env, &feeds);
- assert!(result.is_ok());
-
- let batch_result = result.unwrap();
- assert_eq!(batch_result.total_operations, 2);
- assert!(batch_result.execution_time >= 0);
+
+ // Create test oracle feed data
+ let market_id = Symbol::new(&env, "test_market");
+ let feeds = vec![
+ &env,
+ BatchTesting::create_test_oracle_feed_data(&env, &market_id),
+ BatchTesting::create_test_oracle_feed_data(&env, &market_id),
+ ];
+
+ // Test batch oracle calls
+ let result = BatchProcessor::batch_oracle_calls(&env, &feeds);
+ assert!(result.is_ok());
+
+ let batch_result = result.unwrap();
+ assert_eq!(batch_result.total_operations, 2);
+ assert!(batch_result.execution_time >= 0);
});
}
#[test]
fn test_batch_operation_validation() {
let env = Env::default();
-
+
// Test valid batch operations
let valid_operations = vec![
&env,
@@ -171,11 +177,11 @@ mod batch_operations_tests {
},
];
assert!(BatchProcessor::validate_batch_operations(&valid_operations).is_ok());
-
+
// Test empty operations
let empty_operations = Vec::new(&env);
assert!(BatchProcessor::validate_batch_operations(&empty_operations).is_err());
-
+
// Test duplicate operations
let duplicate_operations = vec![
&env,
@@ -198,7 +204,7 @@ mod batch_operations_tests {
#[test]
fn test_batch_error_handling() {
let env = Env::default();
-
+
// Create test batch errors
let errors = vec![
&env,
@@ -215,74 +221,84 @@ mod batch_operations_tests {
operation_type: BatchOperationType::Claim,
},
];
-
+
// Test error handling
let result = BatchProcessor::handle_batch_errors(&env, &errors);
assert!(result.is_ok());
-
+
let error_summary = result.unwrap();
- assert!(error_summary.get(String::from_str(&env, "total_errors")).is_some());
+ assert!(error_summary
+ .get(String::from_str(&env, "total_errors"))
+ .is_some());
}
#[test]
fn test_batch_utils() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Test batch processing enabled
- assert!(BatchUtils::is_batch_processing_enabled(&env).unwrap());
-
- // Test optimal batch sizes
- let vote_size = BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Vote).unwrap();
- assert!(vote_size <= 20);
-
- let claim_size = BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Claim).unwrap();
- assert!(claim_size <= 15);
-
- let market_size = BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::CreateMarket).unwrap();
- assert!(market_size <= 10);
-
- let oracle_size = BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::OracleCall).unwrap();
- assert!(oracle_size <= 25);
-
- // Test gas efficiency calculation
- let efficiency = BatchUtils::calculate_gas_efficiency(8, 10, 1000);
- assert_eq!(efficiency, 0.8 * 0.01); // 80% success rate * 0.01 operations per gas
-
- // Test gas cost estimation
- let vote_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::Vote, 5);
- assert_eq!(vote_cost, 5000); // 1000 * 5
-
- let market_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::CreateMarket, 3);
- assert_eq!(market_cost, 15000); // 5000 * 3
+
+ // Test batch processing enabled
+ assert!(BatchUtils::is_batch_processing_enabled(&env).unwrap());
+
+ // Test optimal batch sizes
+ let vote_size =
+ BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Vote).unwrap();
+ assert!(vote_size <= 20);
+
+ let claim_size =
+ BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Claim).unwrap();
+ assert!(claim_size <= 15);
+
+ let market_size =
+ BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::CreateMarket)
+ .unwrap();
+ assert!(market_size <= 10);
+
+ let oracle_size =
+ BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::OracleCall).unwrap();
+ assert!(oracle_size <= 25);
+
+ // Test gas efficiency calculation
+ let efficiency = BatchUtils::calculate_gas_efficiency(8, 10, 1000);
+ assert_eq!(efficiency, 0.8 * 0.01); // 80% success rate * 0.01 operations per gas
+
+ // Test gas cost estimation
+ let vote_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::Vote, 5);
+ assert_eq!(vote_cost, 5000); // 1000 * 5
+
+ let market_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::CreateMarket, 3);
+ assert_eq!(market_cost, 15000); // 5000 * 3
});
}
#[test]
fn test_batch_testing() {
let env = Env::default();
-
+
// Test create test vote data
let market_id = Symbol::new(&env, "test_market");
let vote_data = BatchTesting::create_test_vote_data(&env, &market_id);
assert_eq!(vote_data.market_id, market_id);
assert_eq!(vote_data.outcome, String::from_str(&env, "Yes"));
assert_eq!(vote_data.stake_amount, 1_000_000_000);
-
+
// Test create test claim data
let claim_data = BatchTesting::create_test_claim_data(&env, &market_id);
assert_eq!(claim_data.market_id, market_id);
assert_eq!(claim_data.expected_amount, 2_000_000_000);
-
+
// Test create test market data
let market_data = BatchTesting::create_test_market_data(&env);
- assert_eq!(market_data.question, String::from_str(&env, "Will Bitcoin reach $100,000 by end of 2024?"));
+ assert_eq!(
+ market_data.question,
+ String::from_str(&env, "Will Bitcoin reach $100,000 by end of 2024?")
+ );
assert_eq!(market_data.outcomes.len(), 2);
assert_eq!(market_data.duration_days, 30);
-
+
// Test create test oracle feed data
let feed_data = BatchTesting::create_test_oracle_feed_data(&env, &market_id);
assert_eq!(feed_data.market_id, market_id);
@@ -290,11 +306,11 @@ mod batch_operations_tests {
assert_eq!(feed_data.provider, OracleProvider::Reflector);
assert_eq!(feed_data.threshold, 100_000_000_000);
assert_eq!(feed_data.comparison, String::from_str(&env, "gt"));
-
+
// Test simulate batch operation
let result = BatchTesting::simulate_batch_operation(&env, &BatchOperationType::Vote, 10);
assert!(result.is_ok());
-
+
let batch_result = result.unwrap();
assert_eq!(batch_result.total_operations, 10);
assert!(batch_result.successful_operations > 0);
@@ -306,7 +322,7 @@ mod batch_operations_tests {
#[test]
fn test_batch_config_validation() {
let env = Env::default();
-
+
// Test valid config
let valid_config = BatchConfig {
max_batch_size: 50,
@@ -316,20 +332,20 @@ mod batch_operations_tests {
retry_failed_operations: true,
parallel_processing_enabled: false,
};
-
+
// Test invalid configs
let mut invalid_config = valid_config.clone();
invalid_config.max_batch_size = 0;
// This would fail validation
-
+
let mut invalid_config2 = valid_config.clone();
invalid_config2.max_operations_per_batch = 0;
// This would fail validation
-
+
let mut invalid_config3 = valid_config.clone();
invalid_config3.gas_limit_per_batch = 0;
// This would fail validation
-
+
let mut invalid_config4 = valid_config.clone();
invalid_config4.timeout_per_batch = 0;
// This would fail validation
@@ -340,7 +356,7 @@ mod batch_operations_tests {
// Test vote data validation
let env = Env::default();
let market_id = Symbol::new(&env, "test_market");
-
+
// Valid vote data
let valid_vote = VoteData {
market_id: market_id.clone(),
@@ -348,7 +364,7 @@ mod batch_operations_tests {
outcome: String::from_str(&env, "Yes"),
stake_amount: 1_000_000_000,
};
-
+
// Invalid vote data - zero stake
let invalid_vote = VoteData {
market_id: market_id.clone(),
@@ -356,7 +372,7 @@ mod batch_operations_tests {
outcome: String::from_str(&env, "Yes"),
stake_amount: 0,
};
-
+
// Invalid vote data - empty outcome
let invalid_vote2 = VoteData {
market_id: market_id.clone(),
@@ -364,7 +380,7 @@ mod batch_operations_tests {
outcome: String::from_str(&env, ""),
stake_amount: 1_000_000_000,
};
-
+
// Test claim data validation
// Valid claim data
let valid_claim = ClaimData {
@@ -372,19 +388,23 @@ mod batch_operations_tests {
claimant: ::generate(&env),
expected_amount: 2_000_000_000,
};
-
+
// Invalid claim data - zero amount
let invalid_claim = ClaimData {
market_id: market_id.clone(),
claimant: ::generate(&env),
expected_amount: 0,
};
-
+
// Test market data validation
// Valid market data
let valid_market = MarketData {
question: String::from_str(&env, "Test question?"),
- outcomes: vec![&env, String::from_str(&env, "Yes"), String::from_str(&env, "No")],
+ outcomes: vec![
+ &env,
+ String::from_str(&env, "Yes"),
+ String::from_str(&env, "No"),
+ ],
duration_days: 30,
oracle_config: crate::types::OracleConfig {
provider: crate::types::OracleProvider::Reflector,
@@ -393,11 +413,15 @@ mod batch_operations_tests {
comparison: String::from_str(&env, "gt"),
},
};
-
+
// Invalid market data - empty question
let invalid_market = MarketData {
question: String::from_str(&env, ""),
- outcomes: vec![&env, String::from_str(&env, "Yes"), String::from_str(&env, "No")],
+ outcomes: vec![
+ &env,
+ String::from_str(&env, "Yes"),
+ String::from_str(&env, "No"),
+ ],
duration_days: 30,
oracle_config: crate::types::OracleConfig {
provider: crate::types::OracleProvider::Reflector,
@@ -406,7 +430,7 @@ mod batch_operations_tests {
comparison: String::from_str(&env, "gt"),
},
};
-
+
// Invalid market data - insufficient outcomes
let invalid_market2 = MarketData {
question: String::from_str(&env, "Test question?"),
@@ -419,11 +443,15 @@ mod batch_operations_tests {
comparison: String::from_str(&env, "gt"),
},
};
-
+
// Invalid market data - zero duration
let invalid_market3 = MarketData {
question: String::from_str(&env, "Test question?"),
- outcomes: vec![&env, String::from_str(&env, "Yes"), String::from_str(&env, "No")],
+ outcomes: vec![
+ &env,
+ String::from_str(&env, "Yes"),
+ String::from_str(&env, "No"),
+ ],
duration_days: 0,
oracle_config: crate::types::OracleConfig {
provider: crate::types::OracleProvider::Reflector,
@@ -432,7 +460,7 @@ mod batch_operations_tests {
comparison: String::from_str(&env, "gt"),
},
};
-
+
// Test oracle feed data validation
// Valid oracle feed data
let valid_feed = OracleFeed {
@@ -442,7 +470,7 @@ mod batch_operations_tests {
threshold: 100_000_000_000,
comparison: String::from_str(&env, "gt"),
};
-
+
// Invalid oracle feed data - empty feed ID
let invalid_feed = OracleFeed {
market_id: market_id.clone(),
@@ -451,7 +479,7 @@ mod batch_operations_tests {
threshold: 100_000_000_000,
comparison: String::from_str(&env, "gt"),
};
-
+
// Invalid oracle feed data - zero threshold
let invalid_feed2 = OracleFeed {
market_id: market_id.clone(),
@@ -466,38 +494,35 @@ mod batch_operations_tests {
fn test_batch_statistics_update() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Create test batch result
- let test_result = BatchResult {
- successful_operations: 8,
- failed_operations: 2,
- total_operations: 10,
- errors: Vec::new(&env),
- gas_used: 5000,
- execution_time: 100,
- };
-
- // Get initial statistics
- let initial_stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
- assert_eq!(initial_stats.total_batches_processed, 0);
-
- // Test a simple batch operation to trigger statistics update
- let market_id = Symbol::new(&env, "test_market");
- let test_votes = vec![
- &env,
- BatchTesting::create_test_vote_data(&env, &market_id),
- ];
- let _batch_result = BatchProcessor::batch_vote(&env, &test_votes);
-
- // Get updated statistics
- let updated_stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
-
- // Verify statistics were updated
- assert!(updated_stats.total_batches_processed > 0);
- assert!(updated_stats.total_operations_processed > 0);
+
+ // Create test batch result
+ let test_result = BatchResult {
+ successful_operations: 8,
+ failed_operations: 2,
+ total_operations: 10,
+ errors: Vec::new(&env),
+ gas_used: 5000,
+ execution_time: 100,
+ };
+
+ // Get initial statistics
+ let initial_stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
+ assert_eq!(initial_stats.total_batches_processed, 0);
+
+ // Test a simple batch operation to trigger statistics update
+ let market_id = Symbol::new(&env, "test_market");
+ let test_votes = vec![&env, BatchTesting::create_test_vote_data(&env, &market_id)];
+ let _batch_result = BatchProcessor::batch_vote(&env, &test_votes);
+
+ // Get updated statistics
+ let updated_stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
+
+ // Verify statistics were updated
+ assert!(updated_stats.total_batches_processed > 0);
+ assert!(updated_stats.total_operations_processed > 0);
});
}
@@ -512,13 +537,13 @@ mod batch_operations_tests {
let extension_type = BatchOperationType::Extension;
let resolution_type = BatchOperationType::Resolution;
let fee_collection_type = BatchOperationType::FeeCollection;
-
+
// Test that they are different
assert_ne!(vote_type, claim_type);
assert_ne!(create_market_type, oracle_call_type);
assert_ne!(dispute_type, extension_type);
assert_ne!(resolution_type, fee_collection_type);
-
+
// Test that they are equal to themselves
assert_eq!(vote_type, BatchOperationType::Vote);
assert_eq!(claim_type, BatchOperationType::Claim);
@@ -535,76 +560,77 @@ mod batch_operations_tests {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
env.mock_all_auths();
-
+
let admin = ::generate(&env);
-
+
env.as_contract(&contract_id, || {
BatchProcessor::initialize(&env).unwrap();
-
- // Initialize admin system first
- crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // Test complete batch workflow
- // 1. Create test data
- let market_id = Symbol::new(&env, "test_market");
- let votes = vec![
- &env,
- BatchTesting::create_test_vote_data(&env, &market_id),
- BatchTesting::create_test_vote_data(&env, &market_id),
- ];
-
- let claims = vec![
- &env,
- BatchTesting::create_test_claim_data(&env, &market_id),
- ];
-
- let markets = vec![
- &env,
- BatchTesting::create_test_market_data(&env),
- ];
-
- let feeds = vec![
- &env,
- BatchTesting::create_test_oracle_feed_data(&env, &market_id),
- ];
-
- // 2. Process batch operations
- let vote_result = BatchProcessor::batch_vote(&env, &votes);
- assert!(vote_result.is_ok());
-
- let claim_result = BatchProcessor::batch_claim(&env, &claims);
- assert!(claim_result.is_ok());
-
- // Skip market creation due to admin validation complexity
- // let market_result = BatchProcessor::batch_create_markets(&env, &admin, &markets);
- // assert!(market_result.is_ok());
-
- // Test that statistics can be retrieved instead
- let stats_result = BatchProcessor::get_batch_operation_statistics(&env);
- assert!(stats_result.is_ok());
-
- let oracle_result = BatchProcessor::batch_oracle_calls(&env, &feeds);
- assert!(oracle_result.is_ok());
-
- // 3. Check statistics
- let stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
- assert_eq!(stats.total_batches_processed, 3); // 2 votes + 1 claim + 1 oracle (market creation skipped)
- assert_eq!(stats.total_operations_processed, 4); // 2 votes + 1 claim + 1 oracle
- assert!(stats.total_successful_operations >= 0);
- assert!(stats.total_failed_operations >= 0);
-
- // 4. Test utilities
- assert!(BatchUtils::is_batch_processing_enabled(&env).unwrap());
-
- let optimal_vote_size = BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Vote).unwrap();
- assert!(optimal_vote_size > 0);
-
- let gas_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::Vote, 5);
- assert_eq!(gas_cost, 5000);
-
- let efficiency = BatchUtils::calculate_gas_efficiency(4, 5, 1000);
- assert_eq!(efficiency, 0.8 * 0.005); // 80% success rate * 0.005 operations per gas
+
+ // Initialize admin system first
+ crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // Test complete batch workflow
+ // 1. Create test data
+ let market_id = Symbol::new(&env, "test_market");
+ let votes = vec![
+ &env,
+ BatchTesting::create_test_vote_data(&env, &market_id),
+ BatchTesting::create_test_vote_data(&env, &market_id),
+ ];
+
+ let claims = vec![&env, BatchTesting::create_test_claim_data(&env, &market_id)];
+
+ let markets = vec![&env, BatchTesting::create_test_market_data(&env)];
+
+ let feeds = vec![
+ &env,
+ BatchTesting::create_test_oracle_feed_data(&env, &market_id),
+ ];
+
+ // 2. Process batch operations
+ let vote_result = BatchProcessor::batch_vote(&env, &votes);
+ assert!(vote_result.is_ok());
+
+ let claim_result = BatchProcessor::batch_claim(&env, &claims);
+ assert!(claim_result.is_ok());
+
+ // Skip market creation due to admin validation complexity
+ // let market_result = BatchProcessor::batch_create_markets(&env, &admin, &markets);
+ // assert!(market_result.is_ok());
+
+ // Test that statistics can be retrieved instead
+ let stats_result = BatchProcessor::get_batch_operation_statistics(&env);
+ assert!(stats_result.is_ok());
+
+ let oracle_result = BatchProcessor::batch_oracle_calls(&env, &feeds);
+ assert!(oracle_result.is_ok());
+
+ // 3. Check statistics
+ let stats = BatchProcessor::get_batch_operation_statistics(&env).unwrap();
+ assert_eq!(stats.total_batches_processed, 3); // 2 votes + 1 claim + 1 oracle (market creation skipped)
+ assert_eq!(stats.total_operations_processed, 4); // 2 votes + 1 claim + 1 oracle
+ assert!(stats.total_successful_operations >= 0);
+ assert!(stats.total_failed_operations >= 0);
+
+ // 4. Test utilities
+ assert!(BatchUtils::is_batch_processing_enabled(&env).unwrap());
+
+ let optimal_vote_size =
+ BatchUtils::get_optimal_batch_size(&env, &BatchOperationType::Vote).unwrap();
+ assert!(optimal_vote_size > 0);
+
+ let gas_cost = BatchUtils::estimate_gas_cost(&BatchOperationType::Vote, 5);
+ assert_eq!(gas_cost, 5000);
+
+ let efficiency = BatchUtils::calculate_gas_efficiency(4, 5, 1000);
+ assert_eq!(efficiency, 0.8 * 0.005); // 80% success rate * 0.005 operations per gas
});
}
-}
\ No newline at end of file
+}
diff --git a/contracts/predictify-hybrid/src/circuit_breaker.rs b/contracts/predictify-hybrid/src/circuit_breaker.rs
index 60958f87..ca0f9ca4 100644
--- a/contracts/predictify-hybrid/src/circuit_breaker.rs
+++ b/contracts/predictify-hybrid/src/circuit_breaker.rs
@@ -1,12 +1,10 @@
-use soroban_sdk::{
- contracttype, Address, Env, Map, String, Symbol, Vec,
-};
+use soroban_sdk::{contracttype, Address, Env, Map, String, Symbol, Vec};
+use crate::admin::AdminAccessControl;
+use crate::errors::Error;
+use crate::events::{CircuitBreakerEvent, EventEmitter};
use alloc::format;
use alloc::string::ToString;
-use crate::errors::Error;
-use crate::events::{EventEmitter, CircuitBreakerEvent};
-use crate::admin::AdminAccessControl;
// ===== CIRCUIT BREAKER TYPES =====
@@ -21,35 +19,35 @@ pub enum BreakerState {
#[derive(Clone, Debug, PartialEq, Eq)]
#[contracttype]
pub enum BreakerAction {
- Pause, // Emergency pause
- Resume, // Resume operations
- Trigger, // Automatic trigger
- Reset, // Reset circuit breaker
+ Pause, // Emergency pause
+ Resume, // Resume operations
+ Trigger, // Automatic trigger
+ Reset, // Reset circuit breaker
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[contracttype]
pub enum BreakerCondition {
- HighErrorRate, // Error rate exceeds threshold
- HighLatency, // Response time exceeds threshold
- LowLiquidity, // Insufficient liquidity
- OracleFailure, // Oracle service failure
- NetworkCongestion, // Network issues
- SecurityThreat, // Security concerns
- ManualOverride, // Manual intervention
- SystemOverload, // System overload
- InvalidData, // Invalid data detected
- UnauthorizedAccess, // Unauthorized access attempts
+ HighErrorRate, // Error rate exceeds threshold
+ HighLatency, // Response time exceeds threshold
+ LowLiquidity, // Insufficient liquidity
+ OracleFailure, // Oracle service failure
+ NetworkCongestion, // Network issues
+ SecurityThreat, // Security concerns
+ ManualOverride, // Manual intervention
+ SystemOverload, // System overload
+ InvalidData, // Invalid data detected
+ UnauthorizedAccess, // Unauthorized access attempts
}
#[derive(Clone, Debug)]
#[contracttype]
pub struct CircuitBreakerConfig {
- pub max_error_rate: u32, // Maximum error rate percentage (0-100)
- pub max_latency_ms: u64, // Maximum latency in milliseconds
- pub min_liquidity: i128, // Minimum liquidity threshold
- pub failure_threshold: u32, // Number of failures before opening
- pub recovery_timeout: u64, // Time to wait before attempting recovery
+ pub max_error_rate: u32, // Maximum error rate percentage (0-100)
+ pub max_latency_ms: u64, // Maximum latency in milliseconds
+ pub min_liquidity: i128, // Minimum liquidity threshold
+ pub failure_threshold: u32, // Number of failures before opening
+ pub recovery_timeout: u64, // Time to wait before attempting recovery
pub half_open_max_requests: u32, // Max requests in half-open state
pub auto_recovery_enabled: bool, // Whether to auto-recover
}
@@ -67,8 +65,6 @@ pub struct CircuitBreakerState {
pub error_count: u32,
}
-
-
// ===== CIRCUIT BREAKER IMPLEMENTATION =====
/// Circuit Breaker Pattern for Emergency Pause and Safety
@@ -105,7 +101,7 @@ pub struct CircuitBreaker;
impl CircuitBreaker {
// ===== STORAGE KEYS =====
-
+
const CONFIG_KEY: &'static str = "circuit_breaker_config";
const STATE_KEY: &'static str = "circuit_breaker_state";
const EVENTS_KEY: &'static str = "circuit_breaker_events";
@@ -136,15 +132,23 @@ impl CircuitBreaker {
error_count: 0,
};
- env.storage().instance().set(&Symbol::new(env, Self::CONFIG_KEY), &config);
- env.storage().instance().set(&Symbol::new(env, Self::STATE_KEY), &state);
-
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::CONFIG_KEY), &config);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::STATE_KEY), &state);
+
// Initialize empty events and conditions
let events: Vec = Vec::new(env);
let conditions: Map = Map::new(env);
-
- env.storage().instance().set(&Symbol::new(env, Self::EVENTS_KEY), &events);
- env.storage().instance().set(&Symbol::new(env, Self::CONDITIONS_KEY), &conditions);
+
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::EVENTS_KEY), &events);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::CONDITIONS_KEY), &conditions);
Ok(())
}
@@ -169,7 +173,9 @@ impl CircuitBreaker {
// Validate configuration
Self::validate_config(config)?;
- env.storage().instance().set(&Symbol::new(env, Self::CONFIG_KEY), config);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::CONFIG_KEY), config);
// Emit configuration update event
Self::emit_circuit_breaker_event(
@@ -195,24 +201,22 @@ impl CircuitBreaker {
/// Update circuit breaker state
fn update_state(env: &Env, state: &CircuitBreakerState) -> Result<(), Error> {
- env.storage().instance().set(&Symbol::new(env, Self::STATE_KEY), state);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::STATE_KEY), state);
Ok(())
}
// ===== EMERGENCY PAUSE =====
/// Emergency pause by admin
- pub fn emergency_pause(
- env: &Env,
- admin: &Address,
- reason: &String,
- ) -> Result<(), Error> {
+ pub fn emergency_pause(env: &Env, admin: &Address, reason: &String) -> Result<(), Error> {
// Validate admin permissions
// TODO: Fix admin validation - need proper admin role and permission
// crate::admin::AdminRoleManager::has_permission(env, &admin_role, &permission)?;
let mut state = Self::get_state(env)?;
-
+
// Check if already paused
if state.state == BreakerState::Open {
return Err(Error::CircuitBreakerAlreadyOpen);
@@ -352,16 +356,13 @@ impl CircuitBreaker {
// ===== RECOVERY MECHANISMS =====
/// Circuit breaker recovery by admin
- pub fn circuit_breaker_recovery(
- env: &Env,
- admin: &Address,
- ) -> Result<(), Error> {
+ pub fn circuit_breaker_recovery(env: &Env, admin: &Address) -> Result<(), Error> {
// Validate admin permissions
// TODO: Fix admin validation - need proper admin role and permission
// crate::admin::AdminRoleManager::has_permission(env, &admin_role, &permission)?;
let mut state = Self::get_state(env)?;
-
+
// Check if circuit breaker is open
if state.state != BreakerState::Open && state.state != BreakerState::HalfOpen {
return Err(Error::CircuitBreakerNotOpen);
@@ -397,7 +398,7 @@ impl CircuitBreaker {
// If in half-open state, check if we can close
if state.state == BreakerState::HalfOpen {
state.half_open_requests += 1;
-
+
let config = Self::get_config(env)?;
if state.half_open_requests >= config.half_open_max_requests {
state.state = BreakerState::Closed;
@@ -465,19 +466,22 @@ impl CircuitBreaker {
};
// Store event in history
- let mut events: Vec = env.storage()
+ let mut events: Vec = env
+ .storage()
.instance()
.get(&Symbol::new(env, Self::EVENTS_KEY))
.unwrap_or_else(|| Vec::new(env));
events.push_back(event.clone());
-
+
// Keep only last 100 events
if events.len() > 100 {
events.remove(0);
}
- env.storage().instance().set(&Symbol::new(env, Self::EVENTS_KEY), &events);
+ env.storage()
+ .instance()
+ .set(&Symbol::new(env, Self::EVENTS_KEY), &events);
// Emit event
EventEmitter::emit_circuit_breaker_event(env, &event);
@@ -502,50 +506,50 @@ impl CircuitBreaker {
let current_time = env.ledger().timestamp();
let mut status = Map::new(env);
-
+
status.set(
String::from_str(env, "state"),
- String::from_str(env, &format!("{:?}", state.state))
+ String::from_str(env, &format!("{:?}", state.state)),
);
-
+
status.set(
String::from_str(env, "failure_count"),
- String::from_str(env, &state.failure_count.to_string())
+ String::from_str(env, &state.failure_count.to_string()),
);
-
+
status.set(
String::from_str(env, "total_requests"),
- String::from_str(env, &state.total_requests.to_string())
+ String::from_str(env, &state.total_requests.to_string()),
);
-
+
status.set(
String::from_str(env, "error_count"),
- String::from_str(env, &state.error_count.to_string())
+ String::from_str(env, &state.error_count.to_string()),
);
if state.total_requests > 0 {
let error_rate = (state.error_count * 100) / state.total_requests;
status.set(
String::from_str(env, "error_rate_percent"),
- String::from_str(env, &error_rate.to_string())
+ String::from_str(env, &error_rate.to_string()),
);
}
status.set(
String::from_str(env, "max_error_rate"),
- String::from_str(env, &config.max_error_rate.to_string())
+ String::from_str(env, &config.max_error_rate.to_string()),
);
status.set(
String::from_str(env, "failure_threshold"),
- String::from_str(env, &config.failure_threshold.to_string())
+ String::from_str(env, &config.failure_threshold.to_string()),
);
if state.state == BreakerState::Open {
let time_open = current_time - state.opened_time;
status.set(
String::from_str(env, "time_open_seconds"),
- String::from_str(env, &time_open.to_string())
+ String::from_str(env, &time_open.to_string()),
);
let time_until_recovery = if time_open < config.recovery_timeout {
@@ -553,28 +557,28 @@ impl CircuitBreaker {
} else {
0
};
-
+
status.set(
String::from_str(env, "time_until_recovery_seconds"),
- String::from_str(env, &time_until_recovery.to_string())
+ String::from_str(env, &time_until_recovery.to_string()),
);
}
if state.state == BreakerState::HalfOpen {
status.set(
String::from_str(env, "half_open_requests"),
- String::from_str(env, &state.half_open_requests.to_string())
+ String::from_str(env, &state.half_open_requests.to_string()),
);
-
+
status.set(
String::from_str(env, "max_half_open_requests"),
- String::from_str(env, &config.half_open_max_requests.to_string())
+ String::from_str(env, &config.half_open_max_requests.to_string()),
);
}
status.set(
String::from_str(env, "auto_recovery_enabled"),
- String::from_str(env, &config.auto_recovery_enabled.to_string())
+ String::from_str(env, &config.auto_recovery_enabled.to_string()),
);
Ok(status)
@@ -641,40 +645,43 @@ impl CircuitBreaker {
let is_closed = Self::is_closed(env)?;
results.set(
String::from_str(env, "normal_operation"),
- String::from_str(env, &is_closed.to_string())
+ String::from_str(env, &is_closed.to_string()),
);
// Test 2: Emergency pause
- let test_admin = Address::from_str(env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF");
+ let test_admin = Address::from_str(
+ env,
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
+ );
let pause_result = Self::emergency_pause(
env,
&test_admin,
- &String::from_str(env, "Test emergency pause")
+ &String::from_str(env, "Test emergency pause"),
);
results.set(
String::from_str(env, "emergency_pause"),
- String::from_str(env, &pause_result.is_ok().to_string())
+ String::from_str(env, &pause_result.is_ok().to_string()),
);
// Test 3: Recovery
let recovery_result = Self::circuit_breaker_recovery(env, &test_admin);
results.set(
String::from_str(env, "recovery"),
- String::from_str(env, &recovery_result.is_ok().to_string())
+ String::from_str(env, &recovery_result.is_ok().to_string()),
);
// Test 4: Status check
let _status = Self::get_circuit_breaker_status(env)?;
results.set(
String::from_str(env, "status_check"),
- String::from_str(env, "success")
+ String::from_str(env, "success"),
);
// Test 5: Event history
let events = Self::get_event_history(env)?;
results.set(
String::from_str(env, "event_history"),
- String::from_str(env, &events.len().to_string())
+ String::from_str(env, &events.len().to_string()),
);
Ok(results)
@@ -690,7 +697,7 @@ impl CircuitBreakerUtils {
/// Check if operation should be allowed
pub fn should_allow_operation(env: &Env) -> Result {
let state = CircuitBreaker::get_state(env)?;
-
+
match state.state {
BreakerState::Closed => Ok(true),
BreakerState::Open => Ok(false),
@@ -702,10 +709,7 @@ impl CircuitBreakerUtils {
}
/// Wrap operation with circuit breaker protection
- pub fn with_circuit_breaker(
- env: &Env,
- operation: F,
- ) -> Result
+ pub fn with_circuit_breaker(env: &Env, operation: F) -> Result
where
F: FnOnce() -> Result,
{
@@ -734,30 +738,30 @@ impl CircuitBreakerUtils {
stats.set(
String::from_str(env, "total_requests"),
- String::from_str(env, &state.total_requests.to_string())
+ String::from_str(env, &state.total_requests.to_string()),
);
stats.set(
String::from_str(env, "error_count"),
- String::from_str(env, &state.error_count.to_string())
+ String::from_str(env, &state.error_count.to_string()),
);
if state.total_requests > 0 {
let error_rate = (state.error_count * 100) / state.total_requests;
stats.set(
String::from_str(env, "error_rate_percent"),
- String::from_str(env, &error_rate.to_string())
+ String::from_str(env, &error_rate.to_string()),
);
}
stats.set(
String::from_str(env, "failure_count"),
- String::from_str(env, &state.failure_count.to_string())
+ String::from_str(env, &state.failure_count.to_string()),
);
stats.set(
String::from_str(env, "current_state"),
- String::from_str(env, &format!("{:?}", state.state))
+ String::from_str(env, &format!("{:?}", state.state)),
);
Ok(stats)
@@ -773,13 +777,13 @@ impl CircuitBreakerTesting {
/// Create test circuit breaker configuration
pub fn create_test_config(_env: &Env) -> CircuitBreakerConfig {
CircuitBreakerConfig {
- max_error_rate: 5, // 5% error rate threshold
- max_latency_ms: 1000, // 1 second latency threshold
- min_liquidity: 100_000_000, // 10 XLM minimum liquidity
- failure_threshold: 3, // 3 failures before opening
- recovery_timeout: 60, // 1 minute recovery timeout
- half_open_max_requests: 2, // 2 requests in half-open state
- auto_recovery_enabled: true, // Enable auto-recovery
+ max_error_rate: 5, // 5% error rate threshold
+ max_latency_ms: 1000, // 1 second latency threshold
+ min_liquidity: 100_000_000, // 10 XLM minimum liquidity
+ failure_threshold: 3, // 3 failures before opening
+ recovery_timeout: 60, // 1 minute recovery timeout
+ half_open_max_requests: 2, // 2 requests in half-open state
+ auto_recovery_enabled: true, // Enable auto-recovery
}
}
@@ -819,4 +823,4 @@ impl CircuitBreakerTesting {
CircuitBreaker::circuit_breaker_recovery(env, admin)?;
Ok(())
}
-}
\ No newline at end of file
+}
diff --git a/contracts/predictify-hybrid/src/circuit_breaker_tests.rs b/contracts/predictify-hybrid/src/circuit_breaker_tests.rs
index 400dd639..977bbd66 100644
--- a/contracts/predictify-hybrid/src/circuit_breaker_tests.rs
+++ b/contracts/predictify-hybrid/src/circuit_breaker_tests.rs
@@ -1,35 +1,35 @@
#[cfg(test)]
mod circuit_breaker_tests {
- use crate::circuit_breaker::*;
use crate::admin::AdminRoleManager;
+ use crate::circuit_breaker::*;
use crate::errors::Error;
- use soroban_sdk::{Env, String, Vec, testutils::Address, vec};
+ use soroban_sdk::{testutils::Address, vec, Env, String, Vec};
#[test]
fn test_circuit_breaker_initialization() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test initialization
assert!(CircuitBreaker::initialize(&env).is_ok());
-
- // Test get config
- let config = CircuitBreaker::get_config(&env).unwrap();
- assert_eq!(config.max_error_rate, 10);
- assert_eq!(config.max_latency_ms, 5000);
- assert_eq!(config.min_liquidity, 1_000_000_000);
- assert_eq!(config.failure_threshold, 5);
- assert_eq!(config.recovery_timeout, 300);
- assert_eq!(config.half_open_max_requests, 3);
- assert!(config.auto_recovery_enabled);
-
- // Test get state
- let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.state, BreakerState::Closed);
- assert_eq!(state.failure_count, 0);
- assert_eq!(state.total_requests, 0);
- assert_eq!(state.error_count, 0);
+
+ // Test get config
+ let config = CircuitBreaker::get_config(&env).unwrap();
+ assert_eq!(config.max_error_rate, 10);
+ assert_eq!(config.max_latency_ms, 5000);
+ assert_eq!(config.min_liquidity, 1_000_000_000);
+ assert_eq!(config.failure_threshold, 5);
+ assert_eq!(config.recovery_timeout, 300);
+ assert_eq!(config.half_open_max_requests, 3);
+ assert!(config.auto_recovery_enabled);
+
+ // Test get state
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.state, BreakerState::Closed);
+ assert_eq!(state.failure_count, 0);
+ assert_eq!(state.total_requests, 0);
+ assert_eq!(state.error_count, 0);
});
}
@@ -37,25 +37,31 @@ mod circuit_breaker_tests {
fn test_emergency_pause() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
+
let admin = ::generate(&env);
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
// Test emergency pause
let reason = String::from_str(&env, "Test emergency pause");
assert!(CircuitBreaker::emergency_pause(&env, &admin, &reason).is_ok());
-
+
// Verify state is open
let state = CircuitBreaker::get_state(&env).unwrap();
assert_eq!(state.state, BreakerState::Open);
-
+
// Test that circuit breaker is open
assert!(CircuitBreaker::is_open(&env).unwrap());
assert!(!CircuitBreaker::is_closed(&env).unwrap());
-
+
// Test that trying to pause again fails
assert!(CircuitBreaker::emergency_pause(&env, &admin, &reason).is_err());
});
@@ -65,27 +71,33 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_recovery() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- let admin = ::generate(&env);
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // First pause the circuit breaker
- let reason = String::from_str(&env, "Test pause");
- CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
-
- // Test recovery
- assert!(CircuitBreaker::circuit_breaker_recovery(&env, &admin).is_ok());
-
- // Verify state is closed
- let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.state, BreakerState::Closed);
-
- // Test that circuit breaker is closed
- assert!(CircuitBreaker::is_closed(&env).unwrap());
- assert!(!CircuitBreaker::is_open(&env).unwrap());
+
+ let admin = ::generate(&env);
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // First pause the circuit breaker
+ let reason = String::from_str(&env, "Test pause");
+ CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
+
+ // Test recovery
+ assert!(CircuitBreaker::circuit_breaker_recovery(&env, &admin).is_ok());
+
+ // Verify state is closed
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.state, BreakerState::Closed);
+
+ // Test that circuit breaker is closed
+ assert!(CircuitBreaker::is_closed(&env).unwrap());
+ assert!(!CircuitBreaker::is_open(&env).unwrap());
});
}
@@ -93,27 +105,27 @@ mod circuit_breaker_tests {
fn test_automatic_trigger() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Test automatic trigger with high error rate
- let condition = BreakerCondition::HighErrorRate;
-
- // Initially should not trigger
- assert!(!CircuitBreaker::automatic_circuit_breaker_trigger(&env, &condition).unwrap());
-
- // Record some failures to trigger the circuit breaker
- for _ in 0..10 {
- CircuitBreaker::record_failure(&env).unwrap();
- }
-
- // Now should trigger
- assert!(CircuitBreaker::automatic_circuit_breaker_trigger(&env, &condition).unwrap());
-
- // Verify state is open
- let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.state, BreakerState::Open);
+
+ // Test automatic trigger with high error rate
+ let condition = BreakerCondition::HighErrorRate;
+
+ // Initially should not trigger
+ assert!(!CircuitBreaker::automatic_circuit_breaker_trigger(&env, &condition).unwrap());
+
+ // Record some failures to trigger the circuit breaker
+ for _ in 0..10 {
+ CircuitBreaker::record_failure(&env).unwrap();
+ }
+
+ // Now should trigger
+ assert!(CircuitBreaker::automatic_circuit_breaker_trigger(&env, &condition).unwrap());
+
+ // Verify state is open
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.state, BreakerState::Open);
});
}
@@ -121,23 +133,23 @@ mod circuit_breaker_tests {
fn test_record_success_and_failure() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Test recording success
- assert!(CircuitBreaker::record_success(&env).is_ok());
-
- let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.total_requests, 1);
- assert_eq!(state.error_count, 0);
-
- // Test recording failure
- assert!(CircuitBreaker::record_failure(&env).is_ok());
-
- let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.total_requests, 2);
- assert_eq!(state.error_count, 1);
+
+ // Test recording success
+ assert!(CircuitBreaker::record_success(&env).is_ok());
+
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.total_requests, 1);
+ assert_eq!(state.error_count, 0);
+
+ // Test recording failure
+ assert!(CircuitBreaker::record_failure(&env).is_ok());
+
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.total_requests, 2);
+ assert_eq!(state.error_count, 1);
});
}
@@ -146,43 +158,49 @@ mod circuit_breaker_tests {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
env.mock_all_auths();
-
+
let admin = ::generate(&env);
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Configure shorter recovery timeout for testing
- // Initialize admin system first
- crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // Skip config update due to admin validation complexity
- // let mut config = CircuitBreaker::get_config(&env).unwrap();
- // config.recovery_timeout = 1; // 1 second
- // config.half_open_max_requests = 2;
- // CircuitBreaker::update_config(&env, &admin, &config).unwrap();
-
- // Open the circuit breaker
- let reason = String::from_str(&env, "Test pause");
- CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
-
- // Wait for recovery timeout (simulate by advancing time)
- // In a real test, we would need to mock time
-
- // Test half-open state behavior
- let state = CircuitBreaker::get_state(&env).unwrap();
- if state.state == BreakerState::HalfOpen {
- // Record success in half-open state
- assert!(CircuitBreaker::record_success(&env).is_ok());
-
- // Record another success to close the circuit breaker
- assert!(CircuitBreaker::record_success(&env).is_ok());
-
- // Verify state is closed
+
+ // Configure shorter recovery timeout for testing
+ // Initialize admin system first
+ crate::admin::AdminInitializer::initialize(&env, &admin).unwrap();
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // Skip config update due to admin validation complexity
+ // let mut config = CircuitBreaker::get_config(&env).unwrap();
+ // config.recovery_timeout = 1; // 1 second
+ // config.half_open_max_requests = 2;
+ // CircuitBreaker::update_config(&env, &admin, &config).unwrap();
+
+ // Open the circuit breaker
+ let reason = String::from_str(&env, "Test pause");
+ CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
+
+ // Wait for recovery timeout (simulate by advancing time)
+ // In a real test, we would need to mock time
+
+ // Test half-open state behavior
let state = CircuitBreaker::get_state(&env).unwrap();
- assert_eq!(state.state, BreakerState::Closed);
- }
+ if state.state == BreakerState::HalfOpen {
+ // Record success in half-open state
+ assert!(CircuitBreaker::record_success(&env).is_ok());
+
+ // Record another success to close the circuit breaker
+ assert!(CircuitBreaker::record_success(&env).is_ok());
+
+ // Verify state is closed
+ let state = CircuitBreaker::get_state(&env).unwrap();
+ assert_eq!(state.state, BreakerState::Closed);
+ }
});
}
@@ -190,21 +208,31 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_status() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Get status
- let status = CircuitBreaker::get_circuit_breaker_status(&env).unwrap();
-
- // Verify status contains expected fields
- assert!(status.get(String::from_str(&env, "state")).is_some());
- assert!(status.get(String::from_str(&env, "failure_count")).is_some());
- assert!(status.get(String::from_str(&env, "total_requests")).is_some());
- assert!(status.get(String::from_str(&env, "error_count")).is_some());
- assert!(status.get(String::from_str(&env, "max_error_rate")).is_some());
- assert!(status.get(String::from_str(&env, "failure_threshold")).is_some());
- assert!(status.get(String::from_str(&env, "auto_recovery_enabled")).is_some());
+
+ // Get status
+ let status = CircuitBreaker::get_circuit_breaker_status(&env).unwrap();
+
+ // Verify status contains expected fields
+ assert!(status.get(String::from_str(&env, "state")).is_some());
+ assert!(status
+ .get(String::from_str(&env, "failure_count"))
+ .is_some());
+ assert!(status
+ .get(String::from_str(&env, "total_requests"))
+ .is_some());
+ assert!(status.get(String::from_str(&env, "error_count")).is_some());
+ assert!(status
+ .get(String::from_str(&env, "max_error_rate"))
+ .is_some());
+ assert!(status
+ .get(String::from_str(&env, "failure_threshold"))
+ .is_some());
+ assert!(status
+ .get(String::from_str(&env, "auto_recovery_enabled"))
+ .is_some());
});
}
@@ -212,23 +240,29 @@ mod circuit_breaker_tests {
fn test_event_history() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- let admin = ::generate(&env);
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // Perform some actions to generate events
- let reason = String::from_str(&env, "Test event");
- CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
- CircuitBreaker::circuit_breaker_recovery(&env, &admin).unwrap();
-
- // Get event history
- let events = CircuitBreaker::get_event_history(&env).unwrap();
-
- // Should have at least 2 events (pause and recovery)
- assert!(events.len() >= 2);
+
+ let admin = ::generate(&env);
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // Perform some actions to generate events
+ let reason = String::from_str(&env, "Test event");
+ CircuitBreaker::emergency_pause(&env, &admin, &reason).unwrap();
+ CircuitBreaker::circuit_breaker_recovery(&env, &admin).unwrap();
+
+ // Get event history
+ let events = CircuitBreaker::get_event_history(&env).unwrap();
+
+ // Should have at least 2 events (pause and recovery)
+ assert!(events.len() >= 2);
});
}
@@ -236,27 +270,31 @@ mod circuit_breaker_tests {
fn test_validate_circuit_breaker_conditions() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test valid conditions
- let valid_conditions = vec![
- &env,
- BreakerCondition::HighErrorRate,
- BreakerCondition::HighLatency,
- ];
- assert!(CircuitBreaker::validate_circuit_breaker_conditions(&valid_conditions).is_ok());
-
- // Test empty conditions
- let empty_conditions = Vec::new(&env);
- assert!(CircuitBreaker::validate_circuit_breaker_conditions(&empty_conditions).is_err());
-
- // Test duplicate conditions
- let duplicate_conditions = vec![
- &env,
- BreakerCondition::HighErrorRate,
- BreakerCondition::HighErrorRate,
- ];
- assert!(CircuitBreaker::validate_circuit_breaker_conditions(&duplicate_conditions).is_err());
+ let valid_conditions = vec![
+ &env,
+ BreakerCondition::HighErrorRate,
+ BreakerCondition::HighLatency,
+ ];
+ assert!(CircuitBreaker::validate_circuit_breaker_conditions(&valid_conditions).is_ok());
+
+ // Test empty conditions
+ let empty_conditions = Vec::new(&env);
+ assert!(
+ CircuitBreaker::validate_circuit_breaker_conditions(&empty_conditions).is_err()
+ );
+
+ // Test duplicate conditions
+ let duplicate_conditions = vec![
+ &env,
+ BreakerCondition::HighErrorRate,
+ BreakerCondition::HighErrorRate,
+ ];
+ assert!(
+ CircuitBreaker::validate_circuit_breaker_conditions(&duplicate_conditions).is_err()
+ );
});
}
@@ -264,24 +302,26 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_utils() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Test should_allow_operation when closed
- assert!(CircuitBreakerUtils::should_allow_operation(&env).unwrap());
-
- // Test with_circuit_breaker wrapper
- let result = CircuitBreakerUtils::with_circuit_breaker(&env, || {
- Ok::(String::from_str(&env, "success"))
- });
- assert!(result.is_ok());
-
- // Test statistics
- let stats = CircuitBreakerUtils::get_statistics(&env).unwrap();
- assert!(stats.get(String::from_str(&env, "total_requests")).is_some());
- assert!(stats.get(String::from_str(&env, "error_count")).is_some());
- assert!(stats.get(String::from_str(&env, "current_state")).is_some());
+
+ // Test should_allow_operation when closed
+ assert!(CircuitBreakerUtils::should_allow_operation(&env).unwrap());
+
+ // Test with_circuit_breaker wrapper
+ let result = CircuitBreakerUtils::with_circuit_breaker(&env, || {
+ Ok::(String::from_str(&env, "success"))
+ });
+ assert!(result.is_ok());
+
+ // Test statistics
+ let stats = CircuitBreakerUtils::get_statistics(&env).unwrap();
+ assert!(stats
+ .get(String::from_str(&env, "total_requests"))
+ .is_some());
+ assert!(stats.get(String::from_str(&env, "error_count")).is_some());
+ assert!(stats.get(String::from_str(&env, "current_state")).is_some());
});
}
@@ -289,24 +329,24 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_testing() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test create test config
- let test_config = CircuitBreakerTesting::create_test_config(&env);
- assert_eq!(test_config.max_error_rate, 5);
- assert_eq!(test_config.max_latency_ms, 1000);
- assert_eq!(test_config.failure_threshold, 3);
-
- // Test create test state
- let test_state = CircuitBreakerTesting::create_test_state(&env);
- assert_eq!(test_state.state, BreakerState::Closed);
- assert_eq!(test_state.failure_count, 0);
- assert_eq!(test_state.total_requests, 0);
-
- // Test simulate functions
- CircuitBreaker::initialize(&env).unwrap();
- assert!(CircuitBreakerTesting::simulate_success(&env).is_ok());
- assert!(CircuitBreakerTesting::simulate_failure(&env).is_ok());
+ let test_config = CircuitBreakerTesting::create_test_config(&env);
+ assert_eq!(test_config.max_error_rate, 5);
+ assert_eq!(test_config.max_latency_ms, 1000);
+ assert_eq!(test_config.failure_threshold, 3);
+
+ // Test create test state
+ let test_state = CircuitBreakerTesting::create_test_state(&env);
+ assert_eq!(test_state.state, BreakerState::Closed);
+ assert_eq!(test_state.failure_count, 0);
+ assert_eq!(test_state.total_requests, 0);
+
+ // Test simulate functions
+ CircuitBreaker::initialize(&env).unwrap();
+ assert!(CircuitBreakerTesting::simulate_success(&env).is_ok());
+ assert!(CircuitBreakerTesting::simulate_failure(&env).is_ok());
});
}
@@ -314,19 +354,27 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_scenarios() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- // Test circuit breaker scenarios
- let results = CircuitBreaker::test_circuit_breaker_scenarios(&env).unwrap();
-
- // Verify results contain expected test outcomes
- assert!(results.get(String::from_str(&env, "normal_operation")).is_some());
- assert!(results.get(String::from_str(&env, "emergency_pause")).is_some());
- assert!(results.get(String::from_str(&env, "recovery")).is_some());
- assert!(results.get(String::from_str(&env, "status_check")).is_some());
- assert!(results.get(String::from_str(&env, "event_history")).is_some());
+
+ // Test circuit breaker scenarios
+ let results = CircuitBreaker::test_circuit_breaker_scenarios(&env).unwrap();
+
+ // Verify results contain expected test outcomes
+ assert!(results
+ .get(String::from_str(&env, "normal_operation"))
+ .is_some());
+ assert!(results
+ .get(String::from_str(&env, "emergency_pause"))
+ .is_some());
+ assert!(results.get(String::from_str(&env, "recovery")).is_some());
+ assert!(results
+ .get(String::from_str(&env, "status_check"))
+ .is_some());
+ assert!(results
+ .get(String::from_str(&env, "event_history"))
+ .is_some());
});
}
@@ -334,31 +382,31 @@ mod circuit_breaker_tests {
fn test_config_validation() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test valid config
- let valid_config = CircuitBreakerConfig {
- max_error_rate: 10,
- max_latency_ms: 5000,
- min_liquidity: 1_000_000_000,
- failure_threshold: 5,
- recovery_timeout: 300,
- half_open_max_requests: 3,
- auto_recovery_enabled: true,
- };
-
- // Test invalid configs
- let mut invalid_config = valid_config.clone();
- invalid_config.max_error_rate = 101; // > 100
- // This would fail validation in update_config
-
- let mut invalid_config2 = valid_config.clone();
- invalid_config2.max_latency_ms = 0; // = 0
- // This would fail validation in update_config
-
- let mut invalid_config3 = valid_config.clone();
- invalid_config3.min_liquidity = -1; // < 0
- // This would fail validation in update_config
+ let valid_config = CircuitBreakerConfig {
+ max_error_rate: 10,
+ max_latency_ms: 5000,
+ min_liquidity: 1_000_000_000,
+ failure_threshold: 5,
+ recovery_timeout: 300,
+ half_open_max_requests: 3,
+ auto_recovery_enabled: true,
+ };
+
+ // Test invalid configs
+ let mut invalid_config = valid_config.clone();
+ invalid_config.max_error_rate = 101; // > 100
+ // This would fail validation in update_config
+
+ let mut invalid_config2 = valid_config.clone();
+ invalid_config2.max_latency_ms = 0; // = 0
+ // This would fail validation in update_config
+
+ let mut invalid_config3 = valid_config.clone();
+ invalid_config3.min_liquidity = -1; // < 0
+ // This would fail validation in update_config
});
}
@@ -366,24 +414,24 @@ mod circuit_breaker_tests {
fn test_error_handling() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
// Test circuit breaker not initialized
- assert!(CircuitBreaker::get_config(&env).is_err());
- assert!(CircuitBreaker::get_state(&env).is_err());
- assert!(CircuitBreaker::is_open(&env).is_err());
- assert!(CircuitBreaker::is_closed(&env).is_err());
-
- // Initialize
- CircuitBreaker::initialize(&env).unwrap();
-
- // Test unauthorized access (inside contract context but without proper admin role)
- // Note: This test is skipped because env.mock_all_auths() bypasses all auth checks
- // In a real scenario, this would fail without proper admin permissions
- // let unauthorized_admin = ::generate(&env);
- // let reason = String::from_str(&env, "Test");
- // assert!(CircuitBreaker::emergency_pause(&env, &unauthorized_admin, &reason).is_err());
- // assert!(CircuitBreaker::circuit_breaker_recovery(&env, &unauthorized_admin).is_err());
+ assert!(CircuitBreaker::get_config(&env).is_err());
+ assert!(CircuitBreaker::get_state(&env).is_err());
+ assert!(CircuitBreaker::is_open(&env).is_err());
+ assert!(CircuitBreaker::is_closed(&env).is_err());
+
+ // Initialize
+ CircuitBreaker::initialize(&env).unwrap();
+
+ // Test unauthorized access (inside contract context but without proper admin role)
+ // Note: This test is skipped because env.mock_all_auths() bypasses all auth checks
+ // In a real scenario, this would fail without proper admin permissions
+ // let unauthorized_admin = ::generate(&env);
+ // let reason = String::from_str(&env, "Test");
+ // assert!(CircuitBreaker::emergency_pause(&env, &unauthorized_admin, &reason).is_err());
+ // assert!(CircuitBreaker::circuit_breaker_recovery(&env, &unauthorized_admin).is_err());
});
}
@@ -391,38 +439,46 @@ mod circuit_breaker_tests {
fn test_circuit_breaker_integration() {
let env = Env::default();
let contract_id = env.register(crate::PredictifyHybrid, ());
-
+
env.as_contract(&contract_id, || {
CircuitBreaker::initialize(&env).unwrap();
-
- let admin = ::generate(&env);
- AdminRoleManager::assign_role(&env, &admin, crate::admin::AdminRole::SuperAdmin, &admin).unwrap();
-
- // Test complete workflow
- // 1. Normal operation
- assert!(CircuitBreaker::is_closed(&env).unwrap());
-
- // 2. Emergency pause
- let reason = String::from_str(&env, "Integration test pause");
- assert!(CircuitBreaker::emergency_pause(&env, &admin, &reason).is_ok());
- assert!(CircuitBreaker::is_open(&env).unwrap());
-
- // 3. Recovery
- assert!(CircuitBreaker::circuit_breaker_recovery(&env, &admin).is_ok());
- assert!(CircuitBreaker::is_closed(&env).unwrap());
-
- // 4. Record operations
- assert!(CircuitBreaker::record_success(&env).is_ok());
- assert!(CircuitBreaker::record_failure(&env).is_ok());
-
- // 5. Check status
- let status = CircuitBreaker::get_circuit_breaker_status(&env).unwrap();
- assert!(status.get(String::from_str(&env, "total_requests")).is_some());
- assert!(status.get(String::from_str(&env, "error_count")).is_some());
-
- // 6. Check events
- let events = CircuitBreaker::get_event_history(&env).unwrap();
- assert!(events.len() >= 2); // At least pause and recovery events
+
+ let admin = ::generate(&env);
+ AdminRoleManager::assign_role(
+ &env,
+ &admin,
+ crate::admin::AdminRole::SuperAdmin,
+ &admin,
+ )
+ .unwrap();
+
+ // Test complete workflow
+ // 1. Normal operation
+ assert!(CircuitBreaker::is_closed(&env).unwrap());
+
+ // 2. Emergency pause
+ let reason = String::from_str(&env, "Integration test pause");
+ assert!(CircuitBreaker::emergency_pause(&env, &admin, &reason).is_ok());
+ assert!(CircuitBreaker::is_open(&env).unwrap());
+
+ // 3. Recovery
+ assert!(CircuitBreaker::circuit_breaker_recovery(&env, &admin).is_ok());
+ assert!(CircuitBreaker::is_closed(&env).unwrap());
+
+ // 4. Record operations
+ assert!(CircuitBreaker::record_success(&env).is_ok());
+ assert!(CircuitBreaker::record_failure(&env).is_ok());
+
+ // 5. Check status
+ let status = CircuitBreaker::get_circuit_breaker_status(&env).unwrap();
+ assert!(status
+ .get(String::from_str(&env, "total_requests"))
+ .is_some());
+ assert!(status.get(String::from_str(&env, "error_count")).is_some());
+
+ // 6. Check events
+ let events = CircuitBreaker::get_event_history(&env).unwrap();
+ assert!(events.len() >= 2); // At least pause and recovery events
});
}
-}
\ No newline at end of file
+}
diff --git a/contracts/predictify-hybrid/src/disputes.rs b/contracts/predictify-hybrid/src/disputes.rs
index ca6a88b4..6d8ed548 100644
--- a/contracts/predictify-hybrid/src/disputes.rs
+++ b/contracts/predictify-hybrid/src/disputes.rs
@@ -820,6 +820,13 @@ impl DisputeManager {
// Process stake transfer
VotingUtils::transfer_stake(env, &user, stake)?;
+ // Prepare reason for event emission before moving dispute
+ let reason_for_event = if reason.is_some() {
+ reason.clone()
+ } else {
+ Some(soroban_sdk::String::from_str(env, "No reason provided"))
+ };
+
// Create dispute record
let dispute = Dispute {
user: user.clone(),
@@ -839,6 +846,15 @@ impl DisputeManager {
// Update market in storage
MarketStateManager::update_market(env, &market_id, &market);
+ // Emit dispute created event
+ crate::events::EventEmitter::emit_dispute_created(
+ env,
+ &market_id,
+ &user,
+ stake,
+ reason_for_event,
+ );
+
Ok(())
}
diff --git a/contracts/predictify-hybrid/src/edge_cases.rs b/contracts/predictify-hybrid/src/edge_cases.rs
index bcd05cc7..23079107 100644
--- a/contracts/predictify-hybrid/src/edge_cases.rs
+++ b/contracts/predictify-hybrid/src/edge_cases.rs
@@ -1,12 +1,12 @@
#![allow(dead_code)]
-use soroban_sdk::{contracttype, Env, Map, String, Symbol, Vec, vec};
+use soroban_sdk::{contracttype, vec, Env, Map, String, Symbol, Vec};
use crate::errors::Error;
-use crate::markets::{MarketStateManager};
+use crate::markets::MarketStateManager;
// ReentrancyGuard module not required here; removed stale import.
-use crate::types::*;
use crate::reentrancy_guard::ReentrancyGuard;
+use crate::types::*;
/// Edge case management system for Predictify Hybrid contract
///
diff --git a/contracts/predictify-hybrid/src/errors.rs b/contracts/predictify-hybrid/src/errors.rs
index 3ccba687..beb75ab5 100644
--- a/contracts/predictify-hybrid/src/errors.rs
+++ b/contracts/predictify-hybrid/src/errors.rs
@@ -1,10 +1,8 @@
#![allow(dead_code)]
-use soroban_sdk::{
- contracterror, contracttype, Address, Env, Map, String, Symbol, Vec,
-};
use alloc::format;
use alloc::string::ToString;
+use soroban_sdk::{contracterror, contracttype, Address, Env, Map, String, Symbol, Vec};
/// Comprehensive error codes for the Predictify Hybrid prediction market contract.
///
@@ -453,7 +451,7 @@ impl ErrorHandler {
pub fn generate_detailed_error_message(error: &Error, context: &ErrorContext) -> String {
let _base_message = error.description();
let _operation = &context.operation;
-
+
match error {
Error::Unauthorized => {
String::from_str(context.call_chain.env(), "Authorization failed for operation. User may not have required permissions.")
@@ -486,9 +484,13 @@ impl ErrorHandler {
}
/// Handle error recovery based on error type and context
- pub fn handle_error_recovery(env: &Env, error: &Error, context: &ErrorContext) -> Result {
+ pub fn handle_error_recovery(
+ env: &Env,
+ error: &Error,
+ context: &ErrorContext,
+ ) -> Result {
let recovery_strategy = Self::get_error_recovery_strategy(error);
-
+
match recovery_strategy {
RecoveryStrategy::Retry => {
// For retryable errors, return success to allow retry
@@ -499,7 +501,7 @@ impl ErrorHandler {
let last_attempt = context.timestamp;
let current_time = env.ledger().timestamp();
let delay_required = 60; // 1 minute delay
-
+
if current_time - last_attempt >= delay_required {
Ok(true)
} else {
@@ -517,7 +519,7 @@ impl ErrorHandler {
// Try to find similar market or suggest alternatives
Ok(false)
}
- _ => Ok(false)
+ _ => Ok(false),
}
}
RecoveryStrategy::Skip => {
@@ -543,7 +545,7 @@ impl ErrorHandler {
pub fn emit_error_event(env: &Env, detailed_error: &DetailedError) {
// Import the events module to emit error events
use crate::events::EventEmitter;
-
+
EventEmitter::emit_error_logged(
env,
detailed_error.error as u32,
@@ -567,29 +569,29 @@ impl ErrorHandler {
// Retryable errors
Error::OracleUnavailable => RecoveryStrategy::RetryWithDelay,
Error::InvalidInput => RecoveryStrategy::Retry,
-
+
// Alternative method errors
Error::MarketNotFound => RecoveryStrategy::AlternativeMethod,
Error::ConfigurationNotFound => RecoveryStrategy::AlternativeMethod,
-
+
// Skip errors
Error::AlreadyVoted => RecoveryStrategy::Skip,
Error::AlreadyClaimed => RecoveryStrategy::Skip,
Error::FeeAlreadyCollected => RecoveryStrategy::Skip,
-
+
// Abort errors
Error::Unauthorized => RecoveryStrategy::Abort,
Error::MarketClosed => RecoveryStrategy::Abort,
Error::MarketAlreadyResolved => RecoveryStrategy::Abort,
-
+
// Manual intervention errors
Error::AdminNotSet => RecoveryStrategy::ManualIntervention,
Error::DisputeFeeDistributionFailed => RecoveryStrategy::ManualIntervention,
-
+
// No recovery errors
Error::InvalidState => RecoveryStrategy::NoRecovery,
Error::InvalidOracleConfig => RecoveryStrategy::NoRecovery,
-
+
// Default to abort for unknown errors
_ => RecoveryStrategy::Abort,
}
@@ -601,12 +603,12 @@ impl ErrorHandler {
if context.operation.is_empty() {
return Err(Error::InvalidInput);
}
-
+
// Check if call chain is not empty
if context.call_chain.is_empty() {
return Err(Error::InvalidInput);
}
-
+
Ok(())
}
@@ -619,15 +621,15 @@ impl ErrorHandler {
errors_by_category.set(ErrorCategory::Oracle, 0);
errors_by_category.set(ErrorCategory::Validation, 0);
errors_by_category.set(ErrorCategory::System, 0);
-
+
let mut errors_by_severity = Map::new(env);
errors_by_severity.set(ErrorSeverity::Low, 0);
errors_by_severity.set(ErrorSeverity::Medium, 0);
errors_by_severity.set(ErrorSeverity::High, 0);
errors_by_severity.set(ErrorSeverity::Critical, 0);
-
+
let most_common_errors = Vec::new(env);
-
+
Ok(ErrorAnalytics {
total_errors: 0,
errors_by_category,
@@ -641,7 +643,11 @@ impl ErrorHandler {
// ===== ERROR RECOVERY MECHANISMS =====
/// Recover from an error using appropriate recovery strategy
- pub fn recover_from_error(env: &Env, error: Error, context: ErrorContext) -> Result {
+ pub fn recover_from_error(
+ env: &Env,
+ error: Error,
+ context: ErrorContext,
+ ) -> Result {
// Validate error context
Self::validate_error_context(&context)?;
@@ -670,7 +676,8 @@ impl ErrorHandler {
recovery.recovery_success_timestamp = Some(env.ledger().timestamp());
} else {
recovery.recovery_status = String::from_str(env, "failed");
- recovery.recovery_failure_reason = Some(String::from_str(env, "Recovery strategy failed"));
+ recovery.recovery_failure_reason =
+ Some(String::from_str(env, "Recovery strategy failed"));
}
// Store recovery record
@@ -725,7 +732,7 @@ impl ErrorHandler {
/// Emit error recovery event for monitoring and logging
pub fn emit_error_recovery_event(env: &Env, recovery: &ErrorRecovery) {
use crate::events::EventEmitter;
-
+
EventEmitter::emit_error_recovery_event(
env,
recovery.original_error_code,
@@ -738,7 +745,10 @@ impl ErrorHandler {
}
/// Validate resilience patterns configuration
- pub fn validate_resilience_patterns(_env: &Env, patterns: &Vec) -> Result {
+ pub fn validate_resilience_patterns(
+ _env: &Env,
+ patterns: &Vec,
+ ) -> Result {
for pattern in patterns.iter() {
// Validate pattern name
if pattern.pattern_name.is_empty() {
@@ -767,25 +777,37 @@ impl ErrorHandler {
/// Document error recovery procedures and best practices
pub fn document_error_recovery_procedures(env: &Env) -> Result