diff --git a/contracts/predictify-hybrid/src/err.rs b/contracts/predictify-hybrid/src/err.rs index 29996cd1..8213d757 100644 --- a/contracts/predictify-hybrid/src/err.rs +++ b/contracts/predictify-hybrid/src/err.rs @@ -193,8 +193,6 @@ 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. @@ -644,12 +642,6 @@ impl ErrorHandler { Error::ForceResolveAlreadyUsed => { "Force-resolve idempotency key already used. The operation is a safe no-op." } - Error::ForceResolveReplayed => { - "Force-resolve idempotency key already used. Use a new unique key." - } - Error::ForceResolveReasonEmpty => { - "Force-resolve reason is empty. Provide a non-empty reason string." - } _ => "An error occurred. Please verify your parameters and try again.", }; String::from_str(env, msg) @@ -773,9 +765,7 @@ impl ErrorHandler { | Error::AlreadyClaimed | Error::FeeAlreadyCollected | Error::ForceResolveAlreadyUsed => RecoveryStrategy::Skip, - Error::ForceResolveReplayed | Error::ForceResolveReasonEmpty => { - RecoveryStrategy::Retry - } + Error::Unauthorized | Error::MarketClosed | Error::MarketResolved => { RecoveryStrategy::Abort } @@ -1279,7 +1269,9 @@ impl ErrorHandler { /// # Returns /// /// A tuple of (severity, category, recovery_strategy) for the error. - pub(crate) fn get_error_classification(error: &Error) -> (ErrorSeverity, ErrorCategory, RecoveryStrategy) { + pub(crate) fn get_error_classification( + error: &Error, + ) -> (ErrorSeverity, ErrorCategory, RecoveryStrategy) { match error { // Critical Error::AdminNotSet => ( @@ -1349,11 +1341,7 @@ impl ErrorHandler { ErrorCategory::UserOperation, RecoveryStrategy::Skip, ), - Error::ForceResolveReplayed | Error::ForceResolveReasonEmpty => ( - ErrorSeverity::Low, - ErrorCategory::UserOperation, - RecoveryStrategy::Retry, - ), + Error::FeeAlreadyCollected => ( ErrorSeverity::Low, ErrorCategory::Financial, @@ -1478,7 +1466,9 @@ impl Error { "Bets have already been placed on this market (cannot update)" } Error::InsufficientBalance => "Insufficient balance for operation", - Error::InsufficientStorageRent => "Insufficient storage rent for persistent key allocation", + Error::OperationWouldExceedBudget => { + "Operation would exceed the available CPU instruction budget" + } Error::OracleUnavailable => "Oracle is unavailable", Error::InvalidOracleConfig => "Invalid oracle configuration", Error::GasBudgetExceeded => "Gas budget exceeded", @@ -1503,6 +1493,9 @@ impl Error { Error::FeeArithmeticOverflow => "Fee arithmetic overflowed", Error::FeeAlreadyCollected => "Platform fee already collected", Error::NoFeesToCollect => "No fees available to collect", + Error::ForceResolveAlreadyUsed => { + "Force-resolve idempotency key already used for this market" + } Error::InvalidExtensionDays => "Invalid extension days value", Error::ExtensionDenied => "Market extension not allowed", Error::AdminNotSet => "Admin address not set", @@ -1555,14 +1548,22 @@ impl Error { Error::InsufficientStorageRentBudget => { "Insufficient storage rent budget for operation" } - Error::ExtensionCapExceeded => "Cumulative extension cap for this market has been reached", + Error::ExtensionCapExceeded => { + "Cumulative extension cap for this market has been reached" + } Error::UpgradeChainMismatch => "Upgrade chain predecessor hash mismatch", Error::ReplayedOverride => "Admin override nonce replayed; rejected", - Error::AssetDecimalsMismatch => "Asset decimals mismatch between stored and SAC decimals", + Error::AssetDecimalsMismatch => { + "Asset decimals mismatch between stored and SAC decimals" + } Error::DuplicateMarketId => "Market ID already exists in the registry", - Error::CumulativeExtensionCapHit => "Cumulative extension cap reached; no further extensions allowed", + Error::CumulativeExtensionCapHit => { + "Cumulative extension cap reached; no further extensions allowed" + } Error::IllegalMarketStateTransition => "Illegal market state transition attempted", - Error::OracleQuoteOutlier => "Oracle quote is an outlier relative to the rolling median", + Error::OracleQuoteOutlier => { + "Oracle quote is an outlier relative to the rolling median" + } } } @@ -1593,6 +1594,7 @@ impl Error { Error::OracleUnavailable => "ORACLE_UNAVAILABLE", Error::InvalidOracleConfig => "INVALID_ORACLE_CONFIG", Error::GasBudgetExceeded => "GAS_BUDGET_EXCEEDED", + Error::OperationWouldExceedBudget => "OPERATION_WOULD_EXCEED_BUDGET", Error::InvalidQuestion => "INVALID_QUESTION", Error::InvalidOutcomes => "INVALID_OUTCOMES", Error::InvalidDuration => "INVALID_DURATION", @@ -1617,6 +1619,7 @@ impl Error { Error::InvalidExtensionDays => "INVALID_EXTENSION_DAYS", Error::ExtensionDenied => "EXTENSION_DENIED", Error::AdminNotSet => "ADMIN_NOT_SET", + Error::ForceResolveAlreadyUsed => "FORCE_RESOLVE_ALREADY_USED", Error::FeeExceedsMax => "FEE_ABOVE_ACCEPTABLE", Error::OracleStale => "ORACLE_STALE", Error::OracleNoConsensus => "ORACLE_NO_CONSENSUS", @@ -2332,4 +2335,4 @@ mod tests { assert_eq!(recovery.max_recovery_attempts, 2); assert!(recovery.recovery_success_timestamp.is_some()); } -} \ No newline at end of file +} diff --git a/contracts/predictify-hybrid/src/market_creation_validation_tests.rs b/contracts/predictify-hybrid/src/market_creation_validation_tests.rs index 3a651539..0dd76ef0 100644 --- a/contracts/predictify-hybrid/src/market_creation_validation_tests.rs +++ b/contracts/predictify-hybrid/src/market_creation_validation_tests.rs @@ -342,7 +342,7 @@ fn create_market_rejects_overflow_ledger_sequence() { &None, &86_400u64, ); - assert_contract_error(result, Error::InsufficientStorageRent); + assert_contract_error(result, Error::InsufficientStorageRentBudget); } #[test] diff --git a/contracts/predictify-hybrid/src/storage.rs b/contracts/predictify-hybrid/src/storage.rs index 589cb3c1..173e545d 100644 --- a/contracts/predictify-hybrid/src/storage.rs +++ b/contracts/predictify-hybrid/src/storage.rs @@ -2,7 +2,7 @@ use super::*; use crate::markets::{MarketStateLogic, MarketStateManager}; -use crate::types::{Balance, ReflectorAsset, Market, MarketState, OracleConfig}; +use crate::types::{Balance, Market, MarketState, OracleConfig, ReflectorAsset}; use soroban_sdk::{contracttype, Address, Env, IntoVal, Map, Symbol, Val, Vec}; const STORAGE_CONFIG_KEY: &str = "storage_config"; @@ -37,13 +37,13 @@ pub const MARKET_CREATION_PERSISTENT_KEYS: u32 = 1; /// /// # Errors /// -/// Returns [`Error::InsufficientStorageRent`] if the sequence would overflow. +/// Returns [`Error::InsufficientStorageRentBudget`] if the sequence would overflow. pub fn check_market_creation_rent(env: &Env) -> Result<(), Error> { let effective_ttl = MARKET_TTL_LEDGERS.min(env.storage().max_ttl()); let current_seq = env.ledger().sequence(); if current_seq.checked_add(effective_ttl).is_none() { - return Err(Error::InsufficientStorageRent); + return Err(Error::InsufficientStorageRentBudget); } Ok(()) @@ -202,7 +202,7 @@ impl StorageMigration { metadata.admin.require_auth(); let config = StorageOptimizer::get_storage_config(env); - + StorageOptimizer::set_persistent_with_ttl( env, &persistent_key, @@ -253,7 +253,10 @@ impl StorageMigration { market.admin.require_auth(); - let scratch_opt = env.storage().persistent().get::<_, Vec>(&persistent_key); + let scratch_opt = env + .storage() + .persistent() + .get::<_, Vec>(&persistent_key); if let Some(scratch_data) = scratch_opt { let config = StorageOptimizer::get_storage_config(env); @@ -360,12 +363,8 @@ impl StorageOptimizer { .extend_ttl(key, effective_ttl, effective_ttl); } - fn set_persistent_with_ttl( - env: &Env, - key: &K, - value: &V, - desired_ttl_ledgers: u32, - ) where + fn set_persistent_with_ttl(env: &Env, key: &K, value: &V, desired_ttl_ledgers: u32) + where K: IntoVal, V: IntoVal, { @@ -851,7 +850,11 @@ impl StorageOptimizer { } /// Archive market data before deletion - pub(crate) fn archive_market_data(env: &Env, market_id: &Symbol, market: &Market) -> Result<(), Error> { + pub(crate) fn archive_market_data( + env: &Env, + market_id: &Symbol, + market: &Market, + ) -> Result<(), Error> { // Store archived version with timestamp let archive_key = DataKey::ArchivedMarket(market_id.clone(), env.ledger().timestamp()); Self::set_persistent_with_ttl( @@ -915,7 +918,11 @@ impl StorageOptimizer { env: &Env, compressed_market: &CompressedMarket, ) -> Result<(), Error> { - let key = crate::event_archive::derive_archive_key(env, &compressed_market.market_id, "compressed"); + let key = crate::event_archive::derive_archive_key( + env, + &compressed_market.market_id, + "compressed", + ); Self::set_persistent_with_ttl( env, &key, @@ -1263,7 +1270,8 @@ mod tests { BalanceStorage::set_balance(&env, &balance); let key = BalanceStorage::get_key(&env, &user, &asset); - let expected_ttl = StorageOptimizer::persistent_ttl_for_tier(&env, StorageTtlTier::Balance); + let expected_ttl = + StorageOptimizer::persistent_ttl_for_tier(&env, StorageTtlTier::Balance); assert_eq!(env.storage().persistent().get_ttl(&key), expected_ttl); env.ledger().with_mut(|li| { @@ -1288,7 +1296,8 @@ mod tests { env.as_contract(&contract_id, || { EventManager::store_event(&env, &event); let key = EventManager::event_storage_key(&env, &event.id); - let expected_ttl = StorageOptimizer::persistent_ttl_for_tier(&env, StorageTtlTier::Event); + let expected_ttl = + StorageOptimizer::persistent_ttl_for_tier(&env, StorageTtlTier::Event); assert_eq!(env.storage().persistent().get_ttl(&key), expected_ttl); }); } diff --git a/contracts/predictify-hybrid/tests/err_stability.rs b/contracts/predictify-hybrid/tests/err_stability.rs index 36bc2973..5e50e5f5 100644 --- a/contracts/predictify-hybrid/tests/err_stability.rs +++ b/contracts/predictify-hybrid/tests/err_stability.rs @@ -88,7 +88,8 @@ fn general_errors() { assert_eq!(Error::InvalidExtensionDays as u32, 415); assert_eq!(Error::ExtensionDenied as u32, 416); assert_eq!(Error::GasBudgetExceeded as u32, 417); - assert_eq!(Error::AdminNotSet as u32, 418); + assert_eq!(Error::OperationWouldExceedBudget as u32, 418); + assert_eq!(Error::AdminNotSet as u32, 419); assert_eq!(Error::QuestionTooLong as u32, 420); assert_eq!(Error::OutcomeTooLong as u32, 421); assert_eq!(Error::TooManyOutcomes as u32, 422); @@ -104,6 +105,7 @@ fn general_errors() { assert_eq!(Error::TooManyExtensions as u32, 432); assert_eq!(Error::TooManyOracleResults as u32, 433); assert_eq!(Error::TooManyWinningOutcomes as u32, 434); + assert_eq!(Error::ForceResolveAlreadyUsed as u32, 435); assert_eq!(Error::CategoryTooShort as u32, 436); assert_eq!(Error::TagTooShort as u32, 437); assert_eq!(Error::DisputerCannotVote as u32, 438); @@ -111,7 +113,14 @@ fn general_errors() { assert_eq!(Error::DuplicateMarketId as u32, 441); } -// ===== Circuit Breaker Errors (500-508) ===== +// ===== Override / Replay Error ===== + +#[test] +fn override_errors() { + assert_eq!(Error::ReplayedOverride as u32, 526); +} + +// ===== Circuit Breaker Errors (500-527) ===== #[test] fn circuit_breaker_errors() { @@ -124,6 +133,14 @@ fn circuit_breaker_errors() { assert_eq!(Error::CumulativeExtensionCapHit as u32, 506); assert_eq!(Error::IllegalMarketStateTransition as u32, 507); assert_eq!(Error::FeeExceedsMax as u32, 508); + assert_eq!(Error::NoPendingFeeCommit as u32, 519); + assert_eq!(Error::FeeRevealTooEarly as u32, 520); + assert_eq!(Error::FeePreimageMismatch as u32, 521); + assert_eq!(Error::DisputeStakeCapExceeded as u32, 522); + assert_eq!(Error::InsufficientStorageRentBudget as u32, 523); + assert_eq!(Error::ExtensionCapExceeded as u32, 524); + assert_eq!(Error::UpgradeChainMismatch as u32, 525); + assert_eq!(Error::OracleQuoteOutlier as u32, 527); } // ===== Asset decimals ===== @@ -146,4 +163,4 @@ fn total_variant_count() { // update this comment when updating the count. let expected = 93; assert_eq!(std::mem::variant_count::(), expected); -} \ No newline at end of file +}