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
2 changes: 2 additions & 0 deletions contracts/predictify-hybrid/src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ pub enum Error {
// ===== VALIDATION ERRORS (435-437) =====
/// Market ID already exists in the registry. Cannot create duplicate market IDs.
DuplicateMarketId = 441,
/// Override replay detected. Nonce has already been used.
ReplayedOverride = 442,

// ===== CIRCUIT BREAKER ERRORS ====="
/// Circuit breaker has not been initialized. Initialize before use.
Expand Down
4 changes: 4 additions & 0 deletions contracts/predictify-hybrid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3960,6 +3960,8 @@ impl PredictifyHybrid {
max_staleness_secs,
max_confidence_bps,
max_deviation_bps,
max_deviation_z_multiple: None,
history_size: None,
};
crate::oracles::OracleValidationConfigManager::set_global_config(&env, &config)?;

Expand Down Expand Up @@ -3999,6 +4001,8 @@ impl PredictifyHybrid {
max_staleness_secs,
max_confidence_bps,
max_deviation_bps,
max_deviation_z_multiple: None,
history_size: None,
};
crate::oracles::OracleValidationConfigManager::set_event_config(&env, &market_id, &config)?;

Expand Down
2 changes: 1 addition & 1 deletion contracts/predictify-hybrid/src/market_id_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ pub struct MarketIdGenerator;
counters.set(admin.clone(), counter);
env.storage().persistent().set(&key, &counters);
}

}


// ── Tests ─────────────────────────────────────────────────────────────────────
Expand Down
1 change: 1 addition & 0 deletions contracts/predictify-hybrid/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum StorageTtlTier {
pub enum DataKey {
Whitelisted(Address),
Blacklisted(Address),
AdminOverrideNonce(Address),
ArchivedMarket(Symbol, u64),
/// Cumulative days extended for a given market (u32).
MarketExtensionTotal(Symbol),
Expand Down
57 changes: 57 additions & 0 deletions contracts/predictify-hybrid/src/tests/oracle_differential_fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![cfg(test)]

use proptest::prelude::*;
use soroban_sdk::{testutils::Address as _, Address, Env, String, Symbol};
use crate::{
oracles::{OracleInterface, PythOracle, PythFeedConfig, ReflectorOracle},
types::Error,
};

#[derive(Debug, Clone, PartialEq)]
enum OracleOutcome {
Healthy(i128),
Unsupported,
Stale,
Scaled(i128),
UnknownError,
}

fn categorize_outcome(result: Result<i128, Error>) -> OracleOutcome {
match result {
Ok(price) => OracleOutcome::Healthy(price),
Err(Error::OracleUnavailable) => OracleOutcome::Unsupported,
Err(Error::OracleStale) => OracleOutcome::Stale,
Err(_) => OracleOutcome::UnknownError,
}
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]

#[test]
fn test_oracle_differential(
price in 0..i128::MAX,
) {
let env = Env::default();
let admin = Address::generate(&env);

let pyth = PythOracle::new(admin.clone());
let reflector = ReflectorOracle::new(admin.clone());

let feed_id = String::from_str(&env, "BTC/USD");

// Pyth is unsupported on Stellar right now
let pyth_res = categorize_outcome(pyth.get_price(&env, &feed_id));
assert_eq!(pyth_res, OracleOutcome::Unsupported);

// Reflector is the primary oracle but without a mock it will fail
// The differential check here ensures we handle unsupported explicitly
let reflector_res = categorize_outcome(reflector.get_price(&env, &feed_id));

// Either Reflector works (with mock) or returns an error, but Pyth MUST be unsupported
// Let's assert they don't unexpectedly succeed with different valid prices without mock
if let (OracleOutcome::Healthy(p1), OracleOutcome::Healthy(p2)) = (&pyth_res, &reflector_res) {
assert_eq!(p1, p2, "Oracles should not return mismatched healthy prices");
}
}
}
11 changes: 5 additions & 6 deletions contracts/predictify-hybrid/src/upgrade_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,10 +640,10 @@ impl UpgradeManager {
return Ok(true);
}

let verify_count = if depth == 0 || depth > chain.len() {
let verify_count = if depth == 0 || depth > chain.len() as u64 {
chain.len()
} else {
depth
depth as u32
};

let zero_hash = BytesN::from_array(env, &[0u8; 32]);
Expand Down Expand Up @@ -1146,7 +1146,7 @@ mod tests {
fn test_upgrade_proposal_validation() {
let env = Env::default();
let new_wasm_hash = BytesN::from_array(&env, &[1u8; 32]);
let target_version = Version::new(&env, 1, 1, 0, String::from_str(&env, "Upgrade"), false);
let target_version = Version::new(&env, 1, 1, 0, String::from_str(&env, "Upgrade"), false, 0);

let mut proposal = UpgradeProposal::new(
&env,
Expand Down Expand Up @@ -1179,15 +1179,14 @@ mod tests {
// Initialize version
let version_manager = VersionManager::new(&env);
let current_version =
Version::new(&env, 1, 0, 0, String::from_str(&env, "Current"), false);
Version::new(&env, 1, 0, 0, String::from_str(&env, "Current"), false, 0);
version_manager
.track_contract_version(&env, current_version)
.unwrap();

// Create upgrade proposal
let new_wasm_hash = BytesN::from_array(&env, &[1u8; 32]);
let target_version =
Version::new(&env, 1, 1, 0, String::from_str(&env, "Upgrade"), false);
let target_version = Version::new(&env, 1, 1, 0, String::from_str(&env, "Upgrade"), false, 0);

let proposal = UpgradeProposal::new(
&env,
Expand Down
Loading