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
216 changes: 114 additions & 102 deletions contracts/predictify-hybrid/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pub enum Error {
OracleUnavailable = 200,
/// Invalid oracle configuration
InvalidOracleConfig = 201,
/// Fallback oracle is unavailable or unhealthy
FallbackOracleUnavailable = 202,
/// Resolution timeout has been reached
ResolutionTimeoutReached = 203,
/// Refund process has been initiated
RefundStarted = 204,

// ===== VALIDATION ERRORS =====
/// Invalid question format
Expand Down Expand Up @@ -1076,59 +1082,62 @@ impl Error {
/// - **Debugging**: Understand error conditions during development
pub fn description(&self) -> &'static str {
match self {
Error::Unauthorized => "User is not authorized to perform this action",
Error::MarketNotFound => "Market not found",
Error::MarketClosed => "Market is closed",
Error::MarketAlreadyResolved => "Market is already resolved",
Error::MarketNotResolved => "Market is not resolved yet",
Error::NothingToClaim => "User has nothing to claim",
Error::AlreadyClaimed => "User has already claimed",
Error::InsufficientStake => "Insufficient stake amount",
Error::InvalidOutcome => "Invalid outcome choice",
Error::AlreadyVoted => "User has already voted",
Error::AlreadyBet => "User has already placed a bet on this market",
Error::BetsAlreadyPlaced => {
Self::Unauthorized => "User is not authorized to perform this action",
Self::MarketNotFound => "Market not found",
Self::MarketClosed => "Market is closed",
Self::MarketAlreadyResolved => "Market is already resolved",
Self::MarketNotResolved => "Market is not resolved yet",
Self::NothingToClaim => "User has nothing to claim",
Self::AlreadyClaimed => "User has already claimed",
Self::InsufficientStake => "Insufficient stake amount",
Self::InvalidOutcome => "Invalid outcome choice",
Self::AlreadyVoted => "User has already voted",
Self::AlreadyBet => "User has already placed a bet on this market",
Self::BetsAlreadyPlaced => {
"Bets have already been placed on this market (cannot update)"
}
Error::InsufficientBalance => "Insufficient balance for operation",
Error::OracleUnavailable => "Oracle is unavailable",
Error::InvalidOracleConfig => "Invalid oracle configuration",
Error::InvalidQuestion => "Invalid question format",
Error::InvalidOutcomes => "Invalid outcomes provided",
Error::InvalidDuration => "Invalid duration specified",
Error::InvalidThreshold => "Invalid threshold value",
Error::InvalidComparison => "Invalid comparison operator",
Error::InvalidState => "Invalid state",
Error::InvalidInput => "Invalid input",
Error::InvalidFeeConfig => "Invalid fee configuration",
Error::ConfigurationNotFound => "Configuration not found",
Error::AlreadyDisputed => "Already disputed",
Error::DisputeVotingPeriodExpired => "Dispute voting period expired",
Error::DisputeVotingNotAllowed => "Dispute voting not allowed",
Error::DisputeAlreadyVoted => "Already voted in dispute",
Error::DisputeResolutionConditionsNotMet => "Dispute resolution conditions not met",
Error::DisputeFeeDistributionFailed => "Dispute fee distribution failed",
Error::DisputeEscalationNotAllowed => "Dispute escalation not allowed",
Error::ThresholdBelowMinimum => "Threshold below minimum",
Error::ThresholdExceedsMaximum => "Threshold exceeds maximum",
Error::FeeAlreadyCollected => "Fee already collected",
Error::InvalidOracleFeed => "Invalid oracle feed",
Error::NoFeesToCollect => "No fees to collect",
Error::InvalidExtensionDays => "Invalid extension days",
Error::ExtensionDaysExceeded => "Extension days exceeded",
Error::MarketExtensionNotAllowed => "Market extension not allowed",
Error::ExtensionFeeInsufficient => "Extension fee insufficient",
Error::AdminNotSet => "Admin address is not set (initialization missing)",
Error::DisputeTimeoutNotSet => "Dispute timeout not set",

Error::DisputeTimeoutNotExpired => "Dispute timeout not expired",
Error::InvalidTimeoutHours => "Invalid timeout hours",
Error::DisputeTimeoutExtensionNotAllowed => "Dispute timeout extension not allowed",
Error::CircuitBreakerNotInitialized => "Circuit breaker not initialized",
Error::CircuitBreakerAlreadyOpen => "Circuit breaker is already open (paused)",
Error::CircuitBreakerNotOpen => "Circuit breaker is not open (cannot recover)",
Error::CircuitBreakerOpen => "Circuit breaker is open (operations blocked)",
Error::AlreadyInitialized => "Already Initialized",
Self::InsufficientBalance => "Insufficient balance for operation",
Self::OracleUnavailable => "Oracle is unavailable",
Self::InvalidOracleConfig => "Invalid oracle configuration",
Self::FallbackOracleUnavailable => "Fallback oracle is unavailable or unhealthy",
Self::ResolutionTimeoutReached => "Resolution timeout has been reached",
Self::RefundStarted => "Refund process has been initiated",
Self::InvalidQuestion => "Invalid question format",
Self::InvalidOutcomes => "Invalid outcomes provided",
Self::InvalidDuration => "Invalid duration specified",
Self::InvalidThreshold => "Invalid threshold value",
Self::InvalidComparison => "Invalid comparison operator",
Self::InvalidState => "Invalid state",
Self::InvalidInput => "Invalid input",
Self::InvalidFeeConfig => "Invalid fee configuration",
Self::ConfigurationNotFound => "Configuration not found",
Self::AlreadyDisputed => "Already disputed",
Self::DisputeVotingPeriodExpired => "Dispute voting period expired",
Self::DisputeVotingNotAllowed => "Dispute voting not allowed",
Self::DisputeAlreadyVoted => "Already voted in dispute",
Self::DisputeResolutionConditionsNotMet => "Dispute resolution conditions not met",
Self::DisputeFeeDistributionFailed => "Dispute fee distribution failed",
Self::DisputeEscalationNotAllowed => "Dispute escalation not allowed",
Self::ThresholdBelowMinimum => "Threshold below minimum",
Self::ThresholdExceedsMaximum => "Threshold exceeds maximum",
Self::FeeAlreadyCollected => "Fee already collected",
Self::InvalidOracleFeed => "Invalid oracle feed",
Self::NoFeesToCollect => "No fees to collect",
Self::InvalidExtensionDays => "Invalid extension days",
Self::ExtensionDaysExceeded => "Extension days exceeded",
Self::MarketExtensionNotAllowed => "Market extension not allowed",
Self::ExtensionFeeInsufficient => "Extension fee insufficient",
Self::AdminNotSet => "Admin address is not set (initialization missing)",
Self::DisputeTimeoutNotSet => "Dispute timeout not set",

Self::DisputeTimeoutNotExpired => "Dispute timeout not expired",
Self::InvalidTimeoutHours => "Invalid timeout hours",
Self::DisputeTimeoutExtensionNotAllowed => "Dispute timeout extension not allowed",
Self::CircuitBreakerNotInitialized => "Circuit breaker not initialized",
Self::CircuitBreakerAlreadyOpen => "Circuit breaker is already open (paused)",
Self::CircuitBreakerNotOpen => "Circuit breaker is not open (cannot recover)",
Self::CircuitBreakerOpen => "Circuit breaker is open (operations blocked)",
Self::AlreadyInitialized => "Already Initialized",
}
}

Expand Down Expand Up @@ -1197,57 +1206,60 @@ impl Error {
/// - **Testing**: Verify specific error conditions in unit tests
pub fn code(&self) -> &'static str {
match self {
Error::Unauthorized => "UNAUTHORIZED",
Error::MarketNotFound => "MARKET_NOT_FOUND",
Error::MarketClosed => "MARKET_CLOSED",
Error::MarketAlreadyResolved => "MARKET_ALREADY_RESOLVED",
Error::MarketNotResolved => "MARKET_NOT_RESOLVED",
Error::NothingToClaim => "NOTHING_TO_CLAIM",
Error::AlreadyClaimed => "ALREADY_CLAIMED",
Error::InsufficientStake => "INSUFFICIENT_STAKE",
Error::InvalidOutcome => "INVALID_OUTCOME",
Error::AlreadyVoted => "ALREADY_VOTED",
Error::AlreadyBet => "ALREADY_BET",
Error::BetsAlreadyPlaced => "BETS_ALREADY_PLACED",
Error::InsufficientBalance => "INSUFFICIENT_BALANCE",
Error::OracleUnavailable => "ORACLE_UNAVAILABLE",
Error::InvalidOracleConfig => "INVALID_ORACLE_CONFIG",
Error::InvalidQuestion => "INVALID_QUESTION",
Error::InvalidOutcomes => "INVALID_OUTCOMES",
Error::InvalidDuration => "INVALID_DURATION",
Error::InvalidThreshold => "INVALID_THRESHOLD",
Error::InvalidComparison => "INVALID_COMPARISON",
Error::InvalidState => "INVALID_STATE",
Error::InvalidInput => "INVALID_INPUT",
Error::InvalidFeeConfig => "INVALID_FEE_CONFIG",
Error::ConfigurationNotFound => "CONFIGURATION_NOT_FOUND",
Error::AlreadyDisputed => "ALREADY_DISPUTED",
Error::DisputeVotingPeriodExpired => "DISPUTE_VOTING_PERIOD_EXPIRED",
Error::DisputeVotingNotAllowed => "DISPUTE_VOTING_NOT_ALLOWED",
Error::DisputeAlreadyVoted => "DISPUTE_ALREADY_VOTED",
Error::DisputeResolutionConditionsNotMet => "DISPUTE_RESOLUTION_CONDITIONS_NOT_MET",
Error::DisputeFeeDistributionFailed => "DISPUTE_FEE_DISTRIBUTION_FAILED",
Error::DisputeEscalationNotAllowed => "DISPUTE_ESCALATION_NOT_ALLOWED",
Error::ThresholdBelowMinimum => "THRESHOLD_BELOW_MINIMUM",
Error::ThresholdExceedsMaximum => "THRESHOLD_EXCEEDS_MAXIMUM",
Error::FeeAlreadyCollected => "FEE_ALREADY_COLLECTED",
Error::InvalidOracleFeed => "INVALID_ORACLE_FEED",
Error::NoFeesToCollect => "NO_FEES_TO_COLLECT",
Error::InvalidExtensionDays => "INVALID_EXTENSION_DAYS",
Error::ExtensionDaysExceeded => "EXTENSION_DAYS_EXCEEDED",
Error::MarketExtensionNotAllowed => "MARKET_EXTENSION_NOT_ALLOWED",
Error::ExtensionFeeInsufficient => "EXTENSION_FEE_INSUFFICIENT",
Error::AdminNotSet => "ADMIN_NOT_SET",
Error::DisputeTimeoutNotSet => "DISPUTE_TIMEOUT_NOT_SET",

Error::DisputeTimeoutNotExpired => "DISPUTE_TIMEOUT_NOT_EXPIRED",
Error::InvalidTimeoutHours => "INVALID_TIMEOUT_HOURS",
Error::DisputeTimeoutExtensionNotAllowed => "DISPUTE_TIMEOUT_EXTENSION_NOT_ALLOWED",
Error::CircuitBreakerNotInitialized => "CIRCUIT_BREAKER_NOT_INITIALIZED",
Error::CircuitBreakerAlreadyOpen => "CIRCUIT_BREAKER_ALREADY_OPEN",
Error::CircuitBreakerNotOpen => "CIRCUIT_BREAKER_NOT_OPEN",
Error::CircuitBreakerOpen => "CIRCUIT_BREAKER_OPEN",
Error::AlreadyInitialized => "Already_Initialized",
Self::Unauthorized => "UNAUTHORIZED",
Self::MarketNotFound => "MARKET_NOT_FOUND",
Self::MarketClosed => "MARKET_CLOSED",
Self::MarketAlreadyResolved => "MARKET_ALREADY_RESOLVED",
Self::MarketNotResolved => "MARKET_NOT_RESOLVED",
Self::NothingToClaim => "NOTHING_TO_CLAIM",
Self::AlreadyClaimed => "ALREADY_CLAIMED",
Self::InsufficientStake => "INSUFFICIENT_STAKE",
Self::InvalidOutcome => "INVALID_OUTCOME",
Self::AlreadyVoted => "ALREADY_VOTED",
Self::AlreadyBet => "ALREADY_BET",
Self::BetsAlreadyPlaced => "BETS_ALREADY_PLACED",
Self::InsufficientBalance => "INSUFFICIENT_BALANCE",
Self::OracleUnavailable => "ORACLE_UNAVAILABLE",
Self::InvalidOracleConfig => "INVALID_ORACLE_CONFIG",
Self::FallbackOracleUnavailable => "FALLBACK_ORACLE_UNAVAILABLE",
Self::ResolutionTimeoutReached => "RESOLUTION_TIMEOUT_REACHED",
Self::RefundStarted => "REFUND_STARTED",
Self::InvalidQuestion => "INVALID_QUESTION",
Self::InvalidOutcomes => "INVALID_OUTCOMES",
Self::InvalidDuration => "INVALID_DURATION",
Self::InvalidThreshold => "INVALID_THRESHOLD",
Self::InvalidComparison => "INVALID_COMPARISON",
Self::InvalidState => "INVALID_STATE",
Self::InvalidInput => "INVALID_INPUT",
Self::InvalidFeeConfig => "INVALID_FEE_CONFIG",
Self::ConfigurationNotFound => "CONFIGURATION_NOT_FOUND",
Self::AlreadyDisputed => "ALREADY_DISPUTED",
Self::DisputeVotingPeriodExpired => "DISPUTE_VOTING_PERIOD_EXPIRED",
Self::DisputeVotingNotAllowed => "DISPUTE_VOTING_NOT_ALLOWED",
Self::DisputeAlreadyVoted => "DISPUTE_ALREADY_VOTED",
Self::DisputeResolutionConditionsNotMet => "DISPUTE_RESOLUTION_CONDITIONS_NOT_MET",
Self::DisputeFeeDistributionFailed => "DISPUTE_FEE_DISTRIBUTION_FAILED",
Self::DisputeEscalationNotAllowed => "DISPUTE_ESCALATION_NOT_ALLOWED",
Self::ThresholdBelowMinimum => "THRESHOLD_BELOW_MINIMUM",
Self::ThresholdExceedsMaximum => "THRESHOLD_EXCEEDS_MAXIMUM",
Self::FeeAlreadyCollected => "FEE_ALREADY_COLLECTED",
Self::InvalidOracleFeed => "INVALID_ORACLE_FEED",
Self::NoFeesToCollect => "NO_FEES_TO_COLLECT",
Self::InvalidExtensionDays => "INVALID_EXTENSION_DAYS",
Self::ExtensionDaysExceeded => "EXTENSION_DAYS_EXCEEDED",
Self::MarketExtensionNotAllowed => "MARKET_EXTENSION_NOT_ALLOWED",
Self::ExtensionFeeInsufficient => "EXTENSION_FEE_INSUFFICIENT",
Self::AdminNotSet => "ADMIN_NOT_SET",
Self::DisputeTimeoutNotSet => "DISPUTE_TIMEOUT_NOT_SET",

Self::DisputeTimeoutNotExpired => "DISPUTE_TIMEOUT_NOT_EXPIRED",
Self::InvalidTimeoutHours => "INVALID_TIMEOUT_HOURS",
Self::DisputeTimeoutExtensionNotAllowed => "DISPUTE_TIMEOUT_EXTENSION_NOT_ALLOWED",
Self::CircuitBreakerNotInitialized => "CIRCUIT_BREAKER_NOT_INITIALIZED",
Self::CircuitBreakerAlreadyOpen => "CIRCUIT_BREAKER_ALREADY_OPEN",
Self::CircuitBreakerNotOpen => "CIRCUIT_BREAKER_NOT_OPEN",
Self::CircuitBreakerOpen => "CIRCUIT_BREAKER_OPEN",
Self::AlreadyInitialized => "Already_Initialized",
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions contracts/predictify-hybrid/src/event_creation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fn test_create_event_success() {
let end_time = setup.env.ledger().timestamp() + 3600; // 1 hour from now
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
Expand All @@ -63,6 +64,8 @@ fn test_create_event_success() {
&outcomes,
&end_time,
&oracle_config,
&None,
&0,
);

// Verify event details using the new get_event method
Expand All @@ -86,6 +89,7 @@ fn test_create_market_success() {
let duration_days = 30;
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
Expand All @@ -97,6 +101,8 @@ fn test_create_market_success() {
&outcomes,
&duration_days,
&oracle_config,
&None,
&0,
);

assert!(client.get_market(&market_id).is_some());
Expand All @@ -118,6 +124,7 @@ fn test_create_event_unauthorized() {
let end_time = setup.env.ledger().timestamp() + 3600;
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
Expand All @@ -129,6 +136,8 @@ fn test_create_event_unauthorized() {
&outcomes,
&end_time,
&oracle_config,
&None,
&0,
);
}

Expand All @@ -147,6 +156,7 @@ fn test_create_event_invalid_end_time() {
let end_time = setup.env.ledger().timestamp() - 3600; // Past time
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
Expand All @@ -158,6 +168,8 @@ fn test_create_event_invalid_end_time() {
&outcomes,
&end_time,
&oracle_config,
&None,
&0,
);
}

Expand All @@ -172,6 +184,7 @@ fn test_create_event_empty_outcomes() {
let end_time = setup.env.ledger().timestamp() - 3600; // Past time
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
Expand All @@ -183,5 +196,7 @@ fn test_create_event_empty_outcomes() {
&outcomes,
&end_time,
&oracle_config,
&None,
&0,
);
}
51 changes: 51 additions & 0 deletions contracts/predictify-hybrid/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,30 @@ pub struct GovernanceVoteCastEvent {
pub timestamp: u64,
}

/// Event emitted when a fallback oracle is used for market resolution.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FallbackUsedEvent {
/// Market ID
pub market_id: Symbol,
/// Primary oracle address
pub primary_oracle: Address,
/// Fallback oracle address
pub fallback_oracle: Address,
/// Event timestamp
pub timestamp: u64,
}

/// Event emitted when a market resolution timeout is reached.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolutionTimeoutEvent {
/// Market ID
pub market_id: Symbol,
/// Timeout timestamp
pub timeout_timestamp: u64,
}

/// Governance proposal executed event
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -1444,6 +1468,33 @@ impl EventEmitter {
Self::store_event(env, &symbol_short!("mkt_crt"), &event);
}

/// Emit fallback used event
pub fn emit_fallback_used(
env: &Env,
market_id: &Symbol,
primary_oracle: &Address,
fallback_oracle: &Address,
) {
let event = FallbackUsedEvent {
market_id: market_id.clone(),
primary_oracle: primary_oracle.clone(),
fallback_oracle: fallback_oracle.clone(),
timestamp: env.ledger().timestamp(),
};

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

/// Emit resolution timeout event
pub fn emit_resolution_timeout(env: &Env, market_id: &Symbol, timeout_timestamp: u64) {
let event = ResolutionTimeoutEvent {
market_id: market_id.clone(),
timeout_timestamp,
};

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

/// Emit event created event
pub fn emit_event_created(
env: &Env,
Expand Down
Loading
Loading