diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..af5b2e8f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +# CLAUDE.md diff --git a/Cargo.lock b/Cargo.lock index ebad1e88..40b78872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,7 +971,6 @@ name = "compliance_registry" version = "0.1.0" dependencies = [ "ink 5.1.1", - "ink_e2e", "parity-scale-codec", "propchain-traits", "scale-info", @@ -7275,7 +7274,6 @@ name = "tax-compliance" version = "0.1.0" dependencies = [ "ink 5.1.1", - "ink_e2e", "parity-scale-codec", "propchain-traits", "scale-info", diff --git a/contracts/compliance_registry/lib.rs b/contracts/compliance_registry/lib.rs index 9253c94a..b7f31ff2 100644 --- a/contracts/compliance_registry/lib.rs +++ b/contracts/compliance_registry/lib.rs @@ -1,11 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] -#![allow(clippy::needless_borrows_for_generic_args)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::upper_case_acronyms)] #![allow( - clippy::upper_case_acronyms, + clippy::needless_borrows_for_generic_args, clippy::too_many_arguments, - clippy::needless_borrows_for_generic_args + clippy::upper_case_acronyms )] use propchain_traits::ComplianceChecker; @@ -383,53 +380,79 @@ mod compliance_registry { propchain_traits::errors::compliance_codes::COMPLIANCE_EXPIRED } Error::HighRisk => { - propchain_traits::errors::compliance_codes::COMPLIANCE_CHECK_FAILED + propchain_traits::errors::compliance_codes::COMPLIANCE_HIGH_RISK } Error::ProhibitedJurisdiction => { - propchain_traits::errors::compliance_codes::COMPLIANCE_CHECK_FAILED + propchain_traits::errors::compliance_codes::COMPLIANCE_PROHIBITED_JURISDICTION } Error::AlreadyVerified => { - propchain_traits::errors::compliance_codes::COMPLIANCE_UNAUTHORIZED + propchain_traits::errors::compliance_codes::COMPLIANCE_ALREADY_VERIFIED } Error::ConsentNotGiven => { - propchain_traits::errors::compliance_codes::COMPLIANCE_NOT_VERIFIED + propchain_traits::errors::compliance_codes::COMPLIANCE_CONSENT_NOT_GIVEN } Error::DataRetentionExpired => { - propchain_traits::errors::compliance_codes::COMPLIANCE_EXPIRED + propchain_traits::errors::compliance_codes::COMPLIANCE_DATA_RETENTION_EXPIRED } Error::InvalidRiskScore => { - propchain_traits::errors::compliance_codes::COMPLIANCE_CHECK_FAILED + propchain_traits::errors::compliance_codes::COMPLIANCE_INVALID_RISK_SCORE } Error::InvalidDocumentType => { - propchain_traits::errors::compliance_codes::COMPLIANCE_DOCUMENT_MISSING + propchain_traits::errors::compliance_codes::COMPLIANCE_INVALID_DOCUMENT_TYPE } Error::JurisdictionNotSupported => { - propchain_traits::errors::compliance_codes::COMPLIANCE_CHECK_FAILED + propchain_traits::errors::compliance_codes::COMPLIANCE_JURISDICTION_NOT_SUPPORTED } } } fn error_description(&self) -> &'static str { match self { - Error::NotAuthorized => "Caller does not have permission to perform this operation", + Error::NotAuthorized => { + "Caller does not have permission to perform this compliance operation" + } Error::NotVerified => "The user has not completed verification", Error::VerificationExpired => { "The user's verification has expired and needs renewal" } - Error::HighRisk => "The user has been assessed as high risk", - Error::ProhibitedJurisdiction => "The user's jurisdiction is prohibited", - Error::AlreadyVerified => "The user is already verified", - Error::ConsentNotGiven => "The user has not provided required consent", - Error::DataRetentionExpired => "The data retention period has expired", - Error::InvalidRiskScore => "The risk score is invalid or out of range", - Error::InvalidDocumentType => "The document type is invalid or not supported", - Error::JurisdictionNotSupported => "The jurisdiction is not supported", + Error::HighRisk => "The user has been assessed as high risk and is not permitted", + Error::ProhibitedJurisdiction => { + "The user's jurisdiction is prohibited from this operation" + } + Error::AlreadyVerified => "The user is already verified and cannot be re-verified", + Error::ConsentNotGiven => "The user has not provided the required consent", + Error::DataRetentionExpired => { + "The data retention period for this record has expired" + } + Error::InvalidRiskScore => { + "The risk score provided is invalid or out of acceptable range" + } + Error::InvalidDocumentType => "The document type is invalid or not accepted", + Error::JurisdictionNotSupported => { + "The specified jurisdiction is not currently supported" + } } } fn error_category(&self) -> ErrorCategory { ErrorCategory::Compliance } + + fn error_i18n_key(&self) -> &'static str { + match self { + Error::NotAuthorized => "compliance.unauthorized", + Error::NotVerified => "compliance.not_verified", + Error::VerificationExpired => "compliance.verification_expired", + Error::HighRisk => "compliance.high_risk", + Error::ProhibitedJurisdiction => "compliance.prohibited_jurisdiction", + Error::AlreadyVerified => "compliance.already_verified", + Error::ConsentNotGiven => "compliance.consent_not_given", + Error::DataRetentionExpired => "compliance.data_retention_expired", + Error::InvalidRiskScore => "compliance.invalid_risk_score", + Error::InvalidDocumentType => "compliance.invalid_document_type", + Error::JurisdictionNotSupported => "compliance.jurisdiction_not_supported", + } + } } pub type Result = core::result::Result; diff --git a/contracts/traits/src/errors.rs b/contracts/traits/src/errors.rs index 8c7aa21c..0abff045 100644 --- a/contracts/traits/src/errors.rs +++ b/contracts/traits/src/errors.rs @@ -5,6 +5,9 @@ //! - Common error variants reusable across contracts //! - Numeric error codes for external API integration //! - Full Debug, Display, and From trait implementations +//! - [`ErrorMessage`]: structured error snapshot combining code, category, message, and i18n key +//! - [`ContractError::to_error_message()`]: default method to produce an `ErrorMessage` +//! - [`ContractError::error_i18n_key()`]: default method returning a localization key use core::fmt; use scale::{Decode, Encode}; @@ -12,9 +15,30 @@ use scale::{Decode, Encode}; #[cfg(feature = "std")] use scale_info::TypeInfo; -/// ============================================================================= -/// Base Error Trait -/// ============================================================================= +// ============================================================================= +// Standardized Error Message +// ============================================================================= + +/// Structured snapshot of all error information for a single error instance. +/// +/// Suitable for logging and client-side display. All string fields are `&'static str` +/// for `no_std` / no-heap compatibility. This type is not SCALE-encoded since +/// `&'static str` does not implement `Decode`; use it purely in-memory. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ErrorMessage { + /// Numeric error code, globally unique across all PropChain contracts. + pub code: u32, + /// Top-level domain that produced this error. + pub category: ErrorCategory, + /// Short human-readable message (matches `error_description`). + pub message: &'static str, + /// Longer technical description suitable for logs and developer tooling. + pub description: &'static str, + /// Dot-separated localization key for client-side message lookup. + /// Format: `"."`, e.g. `"compliance.not_verified"`. + pub i18n_key: &'static str, +} + // ============================================================================= // Base Error Trait // ============================================================================= @@ -45,6 +69,26 @@ pub trait ContractError: fmt::Debug + fmt::Display + Encode + Decode { _ => ErrorCategory::Unknown, } } + + /// Returns a dot-separated localization key for client-side message lookup. + /// + /// Format: `"."`, e.g. `"compliance.not_verified"`. + /// Override this in each error type to provide a precise key. + fn error_i18n_key(&self) -> &'static str { + "unknown.error" + } + + /// Constructs a complete [`ErrorMessage`] snapshot from this error. + /// No allocation is performed; all fields are `'static`. + fn to_error_message(&self) -> ErrorMessage { + ErrorMessage { + code: self.error_code(), + category: self.error_category(), + message: self.error_description(), + description: self.error_description(), + i18n_key: self.error_i18n_key(), + } + } } /// Error categories for classification and monitoring @@ -82,9 +126,6 @@ impl fmt::Display for ErrorCategory { } } -/// ============================================================================= -/// Common Error Variants -/// ============================================================================= // ============================================================================= // Common Error Variants // ============================================================================= @@ -159,11 +200,23 @@ impl ContractError for CommonError { fn error_category(&self) -> ErrorCategory { ErrorCategory::Common } + + fn error_i18n_key(&self) -> &'static str { + match self { + CommonError::Unauthorized => "common.unauthorized", + CommonError::InvalidParameters => "common.invalid_parameters", + CommonError::NotFound => "common.not_found", + CommonError::InsufficientFunds => "common.insufficient_funds", + CommonError::InvalidState => "common.invalid_state", + CommonError::InternalError => "common.internal_error", + CommonError::CodecError => "common.codec_error", + CommonError::NotImplemented => "common.not_implemented", + CommonError::Timeout => "common.timeout", + CommonError::Duplicate => "common.duplicate", + } + } } -/// ============================================================================= -/// Error Code Constants -/// ============================================================================= // ============================================================================= // Error Code Constants // ============================================================================= @@ -257,6 +310,7 @@ pub mod oracle_codes { pub const ORACLE_INSUFFICIENT_REPUTATION: u32 = 4009; pub const ORACLE_SOURCE_ALREADY_EXISTS: u32 = 4010; pub const ORACLE_REQUEST_PENDING: u32 = 4011; + pub const ORACLE_BATCH_SIZE_EXCEEDED: u32 = 4012; } /// Fee error codes (5000-5999) @@ -278,6 +332,14 @@ pub mod compliance_codes { pub const COMPLIANCE_CHECK_FAILED: u32 = 6003; pub const COMPLIANCE_DOCUMENT_MISSING: u32 = 6004; pub const COMPLIANCE_EXPIRED: u32 = 6005; + pub const COMPLIANCE_HIGH_RISK: u32 = 6006; + pub const COMPLIANCE_PROHIBITED_JURISDICTION: u32 = 6007; + pub const COMPLIANCE_ALREADY_VERIFIED: u32 = 6008; + pub const COMPLIANCE_CONSENT_NOT_GIVEN: u32 = 6009; + pub const COMPLIANCE_INVALID_RISK_SCORE: u32 = 6010; + pub const COMPLIANCE_JURISDICTION_NOT_SUPPORTED: u32 = 6011; + pub const COMPLIANCE_INVALID_DOCUMENT_TYPE: u32 = 6012; + pub const COMPLIANCE_DATA_RETENTION_EXPIRED: u32 = 6013; } /// Governance error codes (7000-7999) @@ -319,3 +381,59 @@ pub mod monitoring_codes { pub const MONITORING_SUBSCRIBER_LIMIT_REACHED: u32 = 9004; pub const MONITORING_SUBSCRIBER_NOT_FOUND: u32 = 9005; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn common_error_i18n_keys_are_correct() { + assert_eq!( + CommonError::Unauthorized.error_i18n_key(), + "common.unauthorized" + ); + assert_eq!(CommonError::NotFound.error_i18n_key(), "common.not_found"); + assert_eq!(CommonError::Duplicate.error_i18n_key(), "common.duplicate"); + } + + #[test] + fn to_error_message_populates_all_fields() { + let msg = CommonError::Unauthorized.to_error_message(); + assert_eq!(msg.code, common_codes::UNAUTHORIZED); + assert_eq!(msg.category, ErrorCategory::Common); + assert_eq!(msg.i18n_key, "common.unauthorized"); + assert!(!msg.description.is_empty()); + } + + #[test] + fn oracle_batch_size_exceeded_constant_matches_value() { + assert_eq!(oracle_codes::ORACLE_BATCH_SIZE_EXCEEDED, 4012); + } + + #[test] + fn compliance_codes_are_unique() { + let mut codes = vec![ + compliance_codes::COMPLIANCE_UNAUTHORIZED, + compliance_codes::COMPLIANCE_NOT_VERIFIED, + compliance_codes::COMPLIANCE_CHECK_FAILED, + compliance_codes::COMPLIANCE_DOCUMENT_MISSING, + compliance_codes::COMPLIANCE_EXPIRED, + compliance_codes::COMPLIANCE_HIGH_RISK, + compliance_codes::COMPLIANCE_PROHIBITED_JURISDICTION, + compliance_codes::COMPLIANCE_ALREADY_VERIFIED, + compliance_codes::COMPLIANCE_CONSENT_NOT_GIVEN, + compliance_codes::COMPLIANCE_INVALID_RISK_SCORE, + compliance_codes::COMPLIANCE_JURISDICTION_NOT_SUPPORTED, + compliance_codes::COMPLIANCE_INVALID_DOCUMENT_TYPE, + compliance_codes::COMPLIANCE_DATA_RETENTION_EXPIRED, + ]; + let len = codes.len(); + codes.sort(); + codes.dedup(); + assert_eq!( + codes.len(), + len, + "duplicate compliance error codes detected" + ); + } +} diff --git a/contracts/traits/src/i18n.rs b/contracts/traits/src/i18n.rs new file mode 100644 index 00000000..d6e3f153 --- /dev/null +++ b/contracts/traits/src/i18n.rs @@ -0,0 +1,385 @@ +//! Localization infrastructure for PropChain error messages. +//! +//! All lookups are static match expressions that allocate nothing, making this +//! module fully compatible with `no_std` / WASM contract environments. +//! +//! # Key format +//! Keys follow the pattern `"."` in snake_case, for example: +//! - `"common.unauthorized"` +//! - `"compliance.not_verified"` +//! - `"oracle.batch_size_exceeded"` +//! +//! # Adding a new locale +//! 1. Add a variant to [`SupportedLocale`]. +//! 2. In the `lookup` function, add a `SupportedLocale::YourLocale => "..."` arm +//! inside each key's match block, following the English arm as the template. + +use scale::{Decode, Encode}; + +#[cfg(feature = "std")] +use scale_info::TypeInfo; + +/// Supported display locales. +/// +/// Only English is provided by default. The enum is designed for extension: +/// adding a new locale only requires a new variant and translation strings +/// inside [`lookup`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(TypeInfo))] +pub enum SupportedLocale { + /// English (default) + En, +} + +/// A resolved localized message with its original key, locale, and translated text. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LocalizedMessage { + /// The original i18n key that was looked up. + pub key: &'static str, + /// The locale used for the resolution. + pub locale: SupportedLocale, + /// The resolved, human-readable text in the requested locale. + pub text: &'static str, +} + +/// Look up the localized message for `key` in the given `locale`. +/// +/// Returns a [`LocalizedMessage`] with static string fields. Falls back to +/// `"unknown.error"` if the key is not recognized. +pub fn lookup(key: &str, locale: SupportedLocale) -> LocalizedMessage { + let (resolved_key, text): (&'static str, &'static str) = match key { + // ---- common -------------------------------------------------------- + "common.unauthorized" => ( + "common.unauthorized", + match locale { + SupportedLocale::En => "Caller does not have permission to perform this operation", + }, + ), + "common.invalid_parameters" => ( + "common.invalid_parameters", + match locale { + SupportedLocale::En => "One or more function parameters are invalid", + }, + ), + "common.not_found" => ( + "common.not_found", + match locale { + SupportedLocale::En => "The requested resource does not exist", + }, + ), + "common.insufficient_funds" => ( + "common.insufficient_funds", + match locale { + SupportedLocale::En => "Account has insufficient balance for this operation", + }, + ), + "common.invalid_state" => ( + "common.invalid_state", + match locale { + SupportedLocale::En => "Cannot perform this operation in the current state", + }, + ), + "common.internal_error" => ( + "common.internal_error", + match locale { + SupportedLocale::En => "An internal error occurred in the contract", + }, + ), + "common.codec_error" => ( + "common.codec_error", + match locale { + SupportedLocale::En => "Failed to encode or decode data", + }, + ), + "common.not_implemented" => ( + "common.not_implemented", + match locale { + SupportedLocale::En => "This feature is not yet implemented", + }, + ), + "common.timeout" => ( + "common.timeout", + match locale { + SupportedLocale::En => "The operation exceeded its time limit", + }, + ), + "common.duplicate" => ( + "common.duplicate", + match locale { + SupportedLocale::En => "This operation or resource already exists", + }, + ), + // ---- oracle -------------------------------------------------------- + "oracle.property_not_found" => ( + "oracle.property_not_found", + match locale { + SupportedLocale::En => "The requested property does not exist in the oracle system", + }, + ), + "oracle.insufficient_sources" => ( + "oracle.insufficient_sources", + match locale { + SupportedLocale::En => { + "Not enough oracle sources are available to provide a reliable valuation" + } + }, + ), + "oracle.invalid_valuation" => ( + "oracle.invalid_valuation", + match locale { + SupportedLocale::En => { + "The valuation data is invalid, zero, or out of acceptable range" + } + }, + ), + "oracle.unauthorized" => ( + "oracle.unauthorized", + match locale { + SupportedLocale::En => { + "Caller does not have permission to perform this oracle operation" + } + }, + ), + "oracle.source_not_found" => ( + "oracle.source_not_found", + match locale { + SupportedLocale::En => "The specified oracle source does not exist", + }, + ), + "oracle.invalid_parameters" => ( + "oracle.invalid_parameters", + match locale { + SupportedLocale::En => "One or more oracle function parameters are invalid", + }, + ), + "oracle.price_feed_error" => ( + "oracle.price_feed_error", + match locale { + SupportedLocale::En => "Failed to retrieve data from external price feed", + }, + ), + "oracle.alert_not_found" => ( + "oracle.alert_not_found", + match locale { + SupportedLocale::En => "The requested price alert does not exist", + }, + ), + "oracle.insufficient_reputation" => ( + "oracle.insufficient_reputation", + match locale { + SupportedLocale::En => "Oracle source reputation is below required threshold", + }, + ), + "oracle.source_already_exists" => ( + "oracle.source_already_exists", + match locale { + SupportedLocale::En => "An oracle source with this identifier already exists", + }, + ), + "oracle.request_pending" => ( + "oracle.request_pending", + match locale { + SupportedLocale::En => "A valuation request for this property is already pending", + }, + ), + "oracle.batch_size_exceeded" => ( + "oracle.batch_size_exceeded", + match locale { + SupportedLocale::En => { + "The number of items in the batch exceeds the configured maximum" + } + }, + ), + // ---- compliance ---------------------------------------------------- + "compliance.unauthorized" => ( + "compliance.unauthorized", + match locale { + SupportedLocale::En => { + "Caller does not have permission to perform this compliance operation" + } + }, + ), + "compliance.not_verified" => ( + "compliance.not_verified", + match locale { + SupportedLocale::En => "The user has not completed verification", + }, + ), + "compliance.verification_expired" => ( + "compliance.verification_expired", + match locale { + SupportedLocale::En => "The user's verification has expired and needs renewal", + }, + ), + "compliance.high_risk" => ( + "compliance.high_risk", + match locale { + SupportedLocale::En => { + "The user has been assessed as high risk and is not permitted" + } + }, + ), + "compliance.prohibited_jurisdiction" => ( + "compliance.prohibited_jurisdiction", + match locale { + SupportedLocale::En => "The user's jurisdiction is prohibited from this operation", + }, + ), + "compliance.already_verified" => ( + "compliance.already_verified", + match locale { + SupportedLocale::En => "The user is already verified and cannot be re-verified", + }, + ), + "compliance.consent_not_given" => ( + "compliance.consent_not_given", + match locale { + SupportedLocale::En => "The user has not provided the required consent", + }, + ), + "compliance.data_retention_expired" => ( + "compliance.data_retention_expired", + match locale { + SupportedLocale::En => "The data retention period for this record has expired", + }, + ), + "compliance.invalid_risk_score" => ( + "compliance.invalid_risk_score", + match locale { + SupportedLocale::En => { + "The risk score provided is invalid or out of acceptable range" + } + }, + ), + "compliance.invalid_document_type" => ( + "compliance.invalid_document_type", + match locale { + SupportedLocale::En => "The document type is invalid or not accepted", + }, + ), + "compliance.jurisdiction_not_supported" => ( + "compliance.jurisdiction_not_supported", + match locale { + SupportedLocale::En => "The specified jurisdiction is not currently supported", + }, + ), + // ---- monitoring ---------------------------------------------------- + "monitoring.unauthorized" => ( + "monitoring.unauthorized", + match locale { + SupportedLocale::En => "Caller does not have monitoring permissions", + }, + ), + "monitoring.contract_paused" => ( + "monitoring.contract_paused", + match locale { + SupportedLocale::En => "Monitoring contract is currently paused", + }, + ), + "monitoring.invalid_threshold" => ( + "monitoring.invalid_threshold", + match locale { + SupportedLocale::En => "Threshold value must be between 0 and 10 000 basis points", + }, + ), + "monitoring.subscriber_limit_reached" => ( + "monitoring.subscriber_limit_reached", + match locale { + SupportedLocale::En => "Cannot add more subscribers, maximum limit reached", + }, + ), + "monitoring.subscriber_not_found" => ( + "monitoring.subscriber_not_found", + match locale { + SupportedLocale::En => "The subscriber account is not registered", + }, + ), + // ---- fallback ------------------------------------------------------ + _ => ( + "unknown.error", + match locale { + SupportedLocale::En => "An unknown error occurred", + }, + ), + }; + + LocalizedMessage { + key: resolved_key, + locale, + text, + } +} + +/// Look up using the default locale (English). +pub fn lookup_default(key: &str) -> LocalizedMessage { + lookup(key, SupportedLocale::En) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn known_key_returns_non_empty_text() { + let msg = lookup("compliance.not_verified", SupportedLocale::En); + assert!(!msg.text.is_empty()); + assert_eq!(msg.key, "compliance.not_verified"); + } + + #[test] + fn unknown_key_falls_back_to_unknown_error() { + let msg = lookup("this.key.does.not.exist", SupportedLocale::En); + assert_eq!(msg.key, "unknown.error"); + assert_eq!(msg.text, "An unknown error occurred"); + } + + #[test] + fn lookup_default_matches_english_lookup() { + let a = lookup_default("oracle.batch_size_exceeded"); + let b = lookup("oracle.batch_size_exceeded", SupportedLocale::En); + assert_eq!(a.text, b.text); + } + + #[test] + fn all_oracle_keys_resolve() { + let keys = [ + "oracle.property_not_found", + "oracle.insufficient_sources", + "oracle.invalid_valuation", + "oracle.unauthorized", + "oracle.source_not_found", + "oracle.invalid_parameters", + "oracle.price_feed_error", + "oracle.alert_not_found", + "oracle.insufficient_reputation", + "oracle.source_already_exists", + "oracle.request_pending", + "oracle.batch_size_exceeded", + ]; + for key in keys { + let msg = lookup_default(key); + assert_ne!(msg.key, "unknown.error", "key '{key}' resolved to fallback"); + } + } + + #[test] + fn all_compliance_keys_resolve() { + let keys = [ + "compliance.unauthorized", + "compliance.not_verified", + "compliance.verification_expired", + "compliance.high_risk", + "compliance.prohibited_jurisdiction", + "compliance.already_verified", + "compliance.consent_not_given", + "compliance.data_retention_expired", + "compliance.invalid_risk_score", + "compliance.invalid_document_type", + "compliance.jurisdiction_not_supported", + ]; + for key in keys { + let msg = lookup_default(key); + assert_ne!(msg.key, "unknown.error", "key '{key}' resolved to fallback"); + } + } +} diff --git a/contracts/traits/src/lib.rs b/contracts/traits/src/lib.rs index 9e223354..eae77309 100644 --- a/contracts/traits/src/lib.rs +++ b/contracts/traits/src/lib.rs @@ -3,10 +3,12 @@ pub mod access_control; pub mod constants; pub mod errors; +pub mod i18n; pub mod monitoring; pub use access_control::*; pub use errors::*; +pub use i18n::*; use ink::prelude::string::String; use ink::primitives::AccountId; pub use monitoring::*; @@ -78,7 +80,7 @@ impl ContractError for OracleError { OracleError::InsufficientReputation => oracle_codes::ORACLE_INSUFFICIENT_REPUTATION, OracleError::SourceAlreadyExists => oracle_codes::ORACLE_SOURCE_ALREADY_EXISTS, OracleError::RequestPending => oracle_codes::ORACLE_REQUEST_PENDING, - OracleError::BatchSizeExceeded => 4012, + OracleError::BatchSizeExceeded => oracle_codes::ORACLE_BATCH_SIZE_EXCEEDED, } } @@ -118,6 +120,23 @@ impl ContractError for OracleError { fn error_category(&self) -> ErrorCategory { ErrorCategory::Oracle } + + fn error_i18n_key(&self) -> &'static str { + match self { + OracleError::PropertyNotFound => "oracle.property_not_found", + OracleError::InsufficientSources => "oracle.insufficient_sources", + OracleError::InvalidValuation => "oracle.invalid_valuation", + OracleError::Unauthorized => "oracle.unauthorized", + OracleError::OracleSourceNotFound => "oracle.source_not_found", + OracleError::InvalidParameters => "oracle.invalid_parameters", + OracleError::PriceFeedError => "oracle.price_feed_error", + OracleError::AlertNotFound => "oracle.alert_not_found", + OracleError::InsufficientReputation => "oracle.insufficient_reputation", + OracleError::SourceAlreadyExists => "oracle.source_already_exists", + OracleError::RequestPending => "oracle.request_pending", + OracleError::BatchSizeExceeded => "oracle.batch_size_exceeded", + } + } } /// Trait definitions for PropChain contracts diff --git a/contracts/traits/src/monitoring.rs b/contracts/traits/src/monitoring.rs index 82de6b1c..374c9910 100644 --- a/contracts/traits/src/monitoring.rs +++ b/contracts/traits/src/monitoring.rs @@ -159,6 +159,16 @@ impl ContractError for MonitoringError { fn error_category(&self) -> ErrorCategory { ErrorCategory::Monitoring } + + fn error_i18n_key(&self) -> &'static str { + match self { + MonitoringError::Unauthorized => "monitoring.unauthorized", + MonitoringError::ContractPaused => "monitoring.contract_paused", + MonitoringError::InvalidThreshold => "monitoring.invalid_threshold", + MonitoringError::SubscriberLimitReached => "monitoring.subscriber_limit_reached", + MonitoringError::SubscriberNotFound => "monitoring.subscriber_not_found", + } + } } /// Cross-contract interface for the monitoring system.