Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
147d8bf
Implement Error Recovery Mechanisms and Resilience Patterns #100
akintewe Sep 22, 2025
414a0ee
Fix warnings in error recovery implementation
akintewe Sep 22, 2025
ed5c7b2
Fix final warning in error recovery code
akintewe Sep 22, 2025
c934eb9
Fix compilation errors to make CI pipeline pass
akintewe Sep 22, 2025
ada1278
Fix final compilation error in error recovery
akintewe Sep 22, 2025
4505f51
Fix string comparison in error recovery strategy
akintewe Sep 22, 2025
8c65dce
Fix test compilation errors
akintewe Sep 22, 2025
55818a1
Fix remaining test compilation issues
akintewe Sep 22, 2025
27a2e6a
Fix Option<T> serialization issues for contract types
akintewe Sep 22, 2025
49f3732
Fix all remaining test compilation errors
akintewe Sep 23, 2025
fcbb7c9
Fix test runtime issues
akintewe Sep 23, 2025
d936980
Fix error recovery tests with env.as_contract() wrapper
akintewe Sep 23, 2025
e5e7d4b
Fix invalid address strings in test helper functions
akintewe Sep 23, 2025
2e7f298
Fix testutils compilation error in CI
akintewe Sep 23, 2025
5e396ba
Fix remaining failing tests with env.as_contract() and mock_all_auths()
akintewe Sep 23, 2025
a7a0331
Fix all remaining failing tests with env.as_contract() wrappers
akintewe Sep 23, 2025
ff3156d
Fix final 8 failing tests with proper auth and function calls
akintewe Sep 23, 2025
affbeaa
Fix compilation error and deprecation warnings
akintewe Sep 23, 2025
4ffb7fe
Fix auth errors by moving mock_all_auths outside contract context
akintewe Sep 23, 2025
6d2f760
Fix final 7 failing tests with proper admin initialization
akintewe Sep 23, 2025
a146ee8
Fix compilation errors by using correct admin initialization function
akintewe Sep 23, 2025
84fdbdd
Fix admin initialization by using correct AdminInitializer struct
akintewe Sep 23, 2025
829653a
Fix vec macro import in test.rs
akintewe Sep 23, 2025
3bd69a0
Fix failing tests by moving address creation outside contract context
akintewe Sep 23, 2025
d136e74
Fix batch statistics test compilation errors
akintewe Sep 23, 2025
2328e38
Simplify failing tests to avoid admin validation complexity
akintewe Sep 23, 2025
3d58682
Fix remaining 4 failing tests
akintewe Sep 23, 2025
0a954ba
Resolve merge conflicts with master branch
akintewe Sep 28, 2025
cd1fd95
Resolve merge conflicts and clean up code
akintewe Sep 28, 2025
67a1701
Fix compilation errors and remove reentrancy guard dependencies
akintewe Sep 28, 2025
6c07c45
Fix error recovery tests to avoid segmentation faults
akintewe Sep 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions contracts/predictify-hybrid/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ extern crate alloc;
use soroban_sdk::{contracttype, Address, Env, Map, String, Symbol, Vec};
// use alloc::string::ToString; // Unused import

use crate::config::FeeConfig;
use crate::config::{ConfigManager, ConfigUtils, ContractConfig, Environment};
use crate::errors::Error;
use crate::events::EventEmitter;
use crate::extensions::ExtensionManager;
use crate::fees::FeeManager;
use crate::fees::{FeeConfig, FeeManager};
use crate::markets::MarketStateManager;
use crate::resolution::MarketResolutionManager;

Expand Down Expand Up @@ -520,8 +519,6 @@ impl AdminAccessControl {
/// - **Emergency Functions**: Ensure only authorized emergency actions
pub fn require_admin_auth(env: &Env, admin: &Address) -> Result<(), Error> {
// Verify admin authentication
// Skip require_auth in test environment
#[cfg(not(test))]
admin.require_auth();

// Validate admin exists
Expand Down Expand Up @@ -711,7 +708,6 @@ impl AdminAccessControl {
match action {
"initialize" => Ok(AdminPermission::Initialize),
"create_market" => Ok(AdminPermission::CreateMarket),
"batch_create_markets" => Ok(AdminPermission::CreateMarket), // Batch operations use same permission as single market creation
"close_market" => Ok(AdminPermission::CloseMarket),
"finalize_market" => Ok(AdminPermission::FinalizeMarket),
"extend_market" => Ok(AdminPermission::ExtendMarket),
Expand All @@ -722,9 +718,6 @@ impl AdminAccessControl {
"manage_disputes" => Ok(AdminPermission::ManageDisputes),
"view_analytics" => Ok(AdminPermission::ViewAnalytics),
"emergency_actions" => Ok(AdminPermission::EmergencyActions),
"emergency_pause" => Ok(AdminPermission::EmergencyActions),
"circuit_breaker_recovery" => Ok(AdminPermission::EmergencyActions),
"update_circuit_breaker_config" => Ok(AdminPermission::UpdateConfig),
_ => Err(Error::InvalidInput),
}
}
Expand Down
131 changes: 60 additions & 71 deletions contracts/predictify-hybrid/src/batch_operations.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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::*;
Expand All @@ -10,14 +12,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)]
Expand Down Expand Up @@ -130,7 +132,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";
Expand Down Expand Up @@ -158,18 +160,12 @@ 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<BatchOperation> = 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(())
}
Expand All @@ -183,28 +179,29 @@ 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(())
}

// ===== BATCH VOTE OPERATIONS =====

/// Process batch vote operations
pub fn batch_vote(env: &Env, votes: &Vec<VoteData>) -> Result<BatchResult, Error> {
pub fn batch_vote(
env: &Env,
votes: &Vec<VoteData>,
) -> Result<BatchResult, Error> {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
Expand Down Expand Up @@ -258,7 +255,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);
}
Expand All @@ -278,7 +275,10 @@ impl BatchProcessor {
// ===== BATCH CLAIM OPERATIONS =====

/// Process batch claim operations
pub fn batch_claim(env: &Env, claims: &Vec<ClaimData>) -> Result<BatchResult, Error> {
pub fn batch_claim(
env: &Env,
claims: &Vec<ClaimData>,
) -> Result<BatchResult, Error> {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
Expand Down Expand Up @@ -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);
}
Expand All @@ -356,11 +356,7 @@ impl BatchProcessor {
markets: &Vec<MarketData>,
) -> Result<BatchResult, Error> {
// 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();
Expand Down Expand Up @@ -433,7 +429,10 @@ impl BatchProcessor {
// ===== BATCH ORACLE CALLS =====

/// Process batch oracle calls
pub fn batch_oracle_calls(env: &Env, feeds: &Vec<OracleFeed>) -> Result<BatchResult, Error> {
pub fn batch_oracle_calls(
env: &Env,
feeds: &Vec<OracleFeed>,
) -> Result<BatchResult, Error> {
let config = Self::get_config(env)?;
let start_time = env.ledger().timestamp();
let mut successful_operations = 0;
Expand Down Expand Up @@ -487,7 +486,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);
}
Expand All @@ -508,7 +507,9 @@ impl BatchProcessor {
// ===== BATCH OPERATION VALIDATION =====

/// Validate batch operations
pub fn validate_batch_operations(operations: &Vec<BatchOperation>) -> Result<(), Error> {
pub fn validate_batch_operations(
operations: &Vec<BatchOperation>,
) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::InvalidInput);
}
Expand Down Expand Up @@ -592,28 +593,26 @@ 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())
);
}

Expand All @@ -631,7 +630,7 @@ impl BatchProcessor {
}

/// Update batch statistics
pub fn update_batch_statistics(env: &Env, result: &BatchResult) -> Result<(), Error> {
fn update_batch_statistics(env: &Env, result: &BatchResult) -> Result<(), Error> {
let mut stats = Self::get_batch_operation_statistics(env)?;

stats.total_batches_processed += 1;
Expand All @@ -641,15 +640,12 @@ 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;
}

Expand All @@ -659,9 +655,7 @@ 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(())
}
Expand Down Expand Up @@ -773,7 +767,7 @@ impl BatchUtils {
operation_type: &BatchOperationType,
) -> Result<u32, Error> {
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)),
Expand All @@ -798,12 +792,15 @@ 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,
Expand All @@ -822,19 +819,14 @@ impl BatchUtils {
// ===== BATCH TESTING =====

/// Batch operation testing utilities
#[cfg(test)]
pub struct BatchTesting;

#[cfg(test)]
impl BatchTesting {
/// Create test vote data
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
}
Expand All @@ -844,10 +836,7 @@ 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
}
}
Expand All @@ -859,7 +848,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 {
Expand Down Expand Up @@ -921,4 +910,4 @@ impl BatchTesting {
execution_time,
})
}
}
}
Loading