Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
162 changes: 162 additions & 0 deletions contracts/predictify-hybrid/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use soroban_sdk::{contracttype, symbol_short, vec, Address, Env, Map, String, Sy

use crate::config::Environment;
use crate::errors::Error;
use crate::types::OracleProvider;

// Define AdminRole locally since it's not available in the crate root
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -767,6 +768,36 @@ pub struct DisputeAutoResolvedEvent {
pub timestamp: u64,
}

/// Governance proposal created event
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GovernanceProposalCreatedEvent {
pub proposal_id: Symbol,
pub proposer: Address,
pub title: String,
pub description: String,
pub timestamp: u64,
}

/// Governance vote cast event
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GovernanceVoteCastEvent {
pub proposal_id: Symbol,
pub voter: Address,
pub support: bool,
pub timestamp: u64,
}

/// Governance proposal executed event
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GovernanceProposalExecutedEvent {
pub proposal_id: Symbol,
pub executor: Address,
pub timestamp: u64,
}

/// Config initialized event
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -819,6 +850,42 @@ pub struct StorageMigrationEvent {
pub timestamp: u64,
}

/// Oracle degradation event - emitted when oracle service fails or becomes unavailable
#[contracttype]
#[derive(Clone, Debug)]
pub struct OracleDegradationEvent {
/// Oracle provider that failed
pub oracle: OracleProvider,
/// Reason for degradation
pub reason: String,
/// Degradation timestamp
pub timestamp: u64,
}

/// Oracle recovery event - emitted when oracle service recovers
#[contracttype]
#[derive(Clone, Debug)]
pub struct OracleRecoveryEvent {
/// Oracle provider that recovered
pub oracle: OracleProvider,
/// Recovery message
pub recovery_message: String,
/// Recovery timestamp
pub timestamp: u64,
}

/// Manual resolution required event - emitted when automatic resolution fails
#[contracttype]
#[derive(Clone, Debug)]
pub struct ManualResolutionRequiredEvent {
/// Market that requires manual resolution
pub market_id: Symbol,
/// Reason manual resolution is needed
pub reason: String,
/// Event timestamp
pub timestamp: u64,
}

/// Event emitted when circuit breaker state changes
///
/// This event provides comprehensive information about circuit breaker
Expand Down Expand Up @@ -1376,6 +1443,101 @@ impl EventEmitter {
Self::store_event(env, &symbol_short!("cb_event"), event);
}

/// Emit oracle degradation event when oracle service fails
pub fn emit_oracle_degradation(
env: &Env,
oracle: &OracleProvider,
reason: &String,
) {
let event = OracleDegradationEvent {
oracle: oracle.clone(),
reason: reason.clone(),
timestamp: env.ledger().timestamp(),
};
Self::store_event(env, &symbol_short!("ora_deg"), &event);
}

/// Emit oracle recovery event when oracle service recovers
pub fn emit_oracle_recovery(
env: &Env,
oracle: &OracleProvider,
message: &String,
) {
let event = OracleRecoveryEvent {
oracle: oracle.clone(),
recovery_message: message.clone(),
timestamp: env.ledger().timestamp(),
};
Self::store_event(env, &symbol_short!("ora_rec"), &event);
}

/// Emit manual resolution required event when automatic resolution fails
pub fn emit_manual_resolution_required(
env: &Env,
market_id: &Symbol,
reason: &String,
) {
let event = ManualResolutionRequiredEvent {
market_id: market_id.clone(),
reason: reason.clone(),
timestamp: env.ledger().timestamp(),
};
Self::store_event(env, &symbol_short!("man_res"), &event);
}

/// Emit governance proposal created event
pub fn emit_governance_proposal_created(
env: &Env,
proposal_id: &Symbol,
proposer: &Address,
title: &String,
description: &String,
) {
let event = GovernanceProposalCreatedEvent {
proposal_id: proposal_id.clone(),
proposer: proposer.clone(),
title: title.clone(),
description: description.clone(),
timestamp: env.ledger().timestamp(),
};

Self::store_event(env, &symbol_short!("gov_prop"), &event);
}

/// Emit governance vote cast event
pub fn emit_governance_vote_cast(
env: &Env,
proposal_id: &Symbol,
voter: &Address,
support: bool,
) {
let timestamp = env.ledger().timestamp();
let event = GovernanceVoteCastEvent {
proposal_id: proposal_id.clone(),
voter: voter.clone(),
support,
timestamp,
};

Self::store_event(env, &symbol_short!("gov_vote"), &event);
}

/// Emit governance proposal executed event
pub fn emit_governance_proposal_executed(
env: &Env,
proposal_id: &Symbol,
executor: &Address,
) {
let timestamp = env.ledger().timestamp();
let event = GovernanceProposalExecutedEvent {
proposal_id: proposal_id.clone(),
executor: executor.clone(),
timestamp,
};

Self::store_event(env, &symbol_short!("gov_exec"), &event);
}

/// Store event in persistent storage
fn store_event<T>(env: &Env, event_key: &Symbol, event_data: &T)
where
Expand Down
2 changes: 0 additions & 2 deletions contracts/predictify-hybrid/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ impl GovernanceContract {
&proposal_id,
&voter,
support,
env.ledger().timestamp(),
);

Ok(())
Expand Down Expand Up @@ -293,7 +292,6 @@ impl GovernanceContract {
&env,
&proposal_id,
&caller,
env.ledger().timestamp(),
);

Ok(())
Expand Down
170 changes: 170 additions & 0 deletions contracts/predictify-hybrid/src/graceful_degradation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#![allow(dead_code)]

use crate::errors::Error;
use crate::events::EventEmitter;
use crate::oracles::{OracleInterface, ReflectorOracle};
use crate::types::OracleProvider;
use soroban_sdk::{contracttype, Address, Env, String, Symbol};

// Basic oracle backup system
pub struct OracleBackup {
primary: OracleProvider,
backup: OracleProvider,
}

impl OracleBackup {
pub fn new(primary: OracleProvider, backup: OracleProvider) -> Self {
Self { primary, backup }
}

// Get price, try backup if primary fails
pub fn get_price(&self, env: &Env, oracle_address: &Address, feed_id: &String) -> Result<i128, Error> {
// Try primary oracle
if let Ok(price) = self.call_oracle(env, &self.primary, oracle_address, feed_id) {
return Ok(price);
}

// Primary failed, notify and try backup
let msg = String::from_str(env, "Primary oracle failed");
EventEmitter::emit_oracle_degradation(env, &self.primary, &msg);

self.call_oracle(env, &self.backup, oracle_address, feed_id)
}

// Call a single oracle
fn call_oracle(&self, env: &Env, oracle: &OracleProvider, address: &Address, feed_id: &String) -> Result<i128, Error> {
match oracle {
OracleProvider::Reflector => {
let reflector = ReflectorOracle::new(address.clone());
reflector.get_price(env, feed_id)
}
_ => Err(Error::OracleUnavailable),
}
}

// Is oracle working?
pub fn is_working(&self, env: &Env, oracle_address: &Address) -> bool {
let test_feed = String::from_str(env, "BTC/USD");
self.call_oracle(env, &self.primary, oracle_address, &test_feed).is_ok()
}
}

// Required functions to match original spec
pub fn fallback_oracle_call(
env: &Env,
primary_oracle: OracleProvider,
fallback_oracle: OracleProvider,
oracle_address: &Address,
feed_id: &String,
) -> Result<i128, Error> {
let backup = OracleBackup::new(primary_oracle, fallback_oracle);
backup.get_price(env, oracle_address, feed_id)
}

pub fn handle_oracle_timeout(oracle: OracleProvider, timeout_seconds: u32, env: &Env) {
if timeout_seconds > 60 {
let msg = String::from_str(env, "Oracle timeout");
EventEmitter::emit_oracle_degradation(env, &oracle, &msg);
}
}

pub fn partial_resolution_mechanism(
env: &Env,
market_id: Symbol,
available_data: PartialData,
) -> Result<String, Error> {
// Good enough confidence? Use the data
if available_data.confidence >= 70 && available_data.price.is_some() {
return Ok(String::from_str(env, "resolved"));
}

// Not good enough, need human
let msg = String::from_str(env, "Need manual resolution");
EventEmitter::emit_manual_resolution_required(env, &market_id, &msg);
Err(Error::OracleUnavailable)
}

pub fn emit_degradation_event(env: &Env, oracle: OracleProvider, reason: String) {
EventEmitter::emit_oracle_degradation(env, &oracle, &reason);
}

pub fn monitor_oracle_health(env: &Env, oracle: OracleProvider, oracle_address: &Address) -> OracleHealth {
let backup = OracleBackup::new(oracle.clone(), oracle);
if backup.is_working(env, oracle_address) {
OracleHealth::Working
} else {
OracleHealth::Broken
}
}

pub fn get_degradation_status(oracle: OracleProvider, env: &Env, oracle_address: &Address) -> OracleHealth {
monitor_oracle_health(env, oracle, oracle_address)
}

pub fn validate_degradation_strategy(_strategy: DegradationStrategy) -> Result<(), Error> {
Ok(()) // All strategies are fine
}

// Simple data types
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DegradationStrategy {
UseBackup,
ManualFix,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OracleHealth {
Working,
Broken,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PartialData {
pub price: Option<i128>,
pub confidence: i128,
pub timestamp: u64,
}

#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::Env;

#[test]
fn can_create_backup() {
let backup = OracleBackup::new(OracleProvider::Reflector, OracleProvider::Pyth);
assert_eq!(backup.primary, OracleProvider::Reflector);
assert_eq!(backup.backup, OracleProvider::Pyth);
}

#[test]
fn can_check_health() {
let env = Env::default();
let addr = Address::generate(&env);
let health = monitor_oracle_health(&env, OracleProvider::Reflector, &addr);
assert!(matches!(health, OracleHealth::Working | OracleHealth::Broken));
}

#[test]
fn strategy_works() {
let result = validate_degradation_strategy(DegradationStrategy::UseBackup);
assert!(result.is_ok());
}

#[test]
fn partial_data_works() {
let env = Env::default();
let market = Symbol::new(&env, "test");
let data = PartialData {
price: Some(100),
confidence: 80,
timestamp: env.ledger().timestamp(),
};
let result = partial_resolution_mechanism(&env, market, data);
assert!(result.is_ok());
}
}
Loading