diff --git a/contracts/analytics/src/lib.rs b/contracts/analytics/src/lib.rs index 72924a6..b972f10 100644 --- a/contracts/analytics/src/lib.rs +++ b/contracts/analytics/src/lib.rs @@ -1,210 +1,233 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(unexpected_cfgs)] -#![allow(clippy::new_without_default)] - -use ink::prelude::string::String; -use ink::prelude::vec::Vec; - -#[ink::contract] -mod propchain_analytics { - use super::*; - - /// Market metrics representing aggregated property data. - #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - pub struct MarketMetrics { - pub average_price: u128, - pub total_volume: u128, - pub properties_listed: u64, - } - - /// Portfolio performance for an individual owner. - #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - #[allow(dead_code)] - pub struct PortfolioPerformance { - pub total_value: u128, - pub property_count: u64, - pub recent_transactions: u64, - } - - /// Trend analysis with historical data. - #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - pub struct MarketTrend { - pub period_start: u64, - pub period_end: u64, - pub price_change_percentage: i32, - pub volume_change_percentage: i32, - } - - /// User behavior analytics for a specific account. - #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - #[allow(dead_code)] - pub struct UserBehavior { - pub account: AccountId, - pub total_interactions: u64, - pub preferred_property_type: String, - pub risk_score: u8, - } - - /// Market Report. - #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - pub struct MarketReport { - pub generated_at: u64, - pub metrics: MarketMetrics, - pub trend: MarketTrend, - pub insights: String, - } - - #[ink(storage)] - pub struct AnalyticsDashboard { - /// Administrator of the analytics dashboard - admin: AccountId, - /// Current market metrics - current_metrics: MarketMetrics, - /// Historical market trends - historical_trends: ink::storage::Mapping, - /// Trend count - trend_count: u64, - } - - impl AnalyticsDashboard { - #[ink(constructor)] - pub fn new() -> Self { - let caller = Self::env().caller(); - Self { - admin: caller, - current_metrics: MarketMetrics { - average_price: 0, - total_volume: 0, - properties_listed: 0, - }, - historical_trends: ink::storage::Mapping::default(), - trend_count: 0, - } - } - - /// Implement property market metrics calculation (average price, volume, etc.) - #[ink(message)] - pub fn get_market_metrics(&self) -> MarketMetrics { - self.current_metrics.clone() - } - - #[ink(message)] - pub fn update_market_metrics(&mut self, average_price: u128, total_volume: u128, properties_listed: u64) { - self.ensure_admin(); - self.current_metrics = MarketMetrics { - average_price, - total_volume, - properties_listed, - }; - } - - /// Create market trend analysis with historical data - #[ink(message)] - pub fn add_market_trend(&mut self, trend: MarketTrend) { - self.ensure_admin(); - self.historical_trends.insert(self.trend_count, &trend); - self.trend_count += 1; - } - - #[ink(message)] - pub fn get_historical_trends(&self) -> Vec { - let mut trends = Vec::new(); - for i in 0..self.trend_count { - if let Some(trend) = self.historical_trends.get(i) { - trends.push(trend); - } - } - trends - } - - /// Create automated market reports generation - #[ink(message)] - pub fn generate_market_report(&self) -> MarketReport { - let latest_trend = if self.trend_count > 0 { - self.historical_trends.get(self.trend_count - 1).unwrap_or(MarketTrend { - period_start: 0, - period_end: 0, - price_change_percentage: 0, - volume_change_percentage: 0, - }) - } else { - MarketTrend { - period_start: 0, - period_end: 0, - price_change_percentage: 0, - volume_change_percentage: 0, - } - }; - - MarketReport { - generated_at: self.env().block_timestamp(), - metrics: self.current_metrics.clone(), - trend: latest_trend, - insights: String::from("Market is relatively stable. Gas optimization is recommended."), - } - } - - /// Add gas usage optimization recommendations - #[ink(message)] - pub fn get_gas_optimization_recommendations(&self) -> String { - String::from("Use batched operations and limit nested looping over dynamic collections (e.g. vectors). Store large items in Mappings instead of Vecs.") - } - - /// Ensure only the admin can modify metrics - fn ensure_admin(&self) { - assert_eq!(self.env().caller(), self.admin, "Unauthorized: Analytics admin only"); - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn market_metrics_defaults() { - let contract = AnalyticsDashboard::new(); - let metrics = contract.get_market_metrics(); - assert_eq!(metrics.average_price, 0); - assert_eq!(metrics.total_volume, 0); - assert_eq!(metrics.properties_listed, 0); - } - - #[ink::test] - fn update_market_metrics_works() { - let mut contract = AnalyticsDashboard::new(); - contract.update_market_metrics(1000, 5000, 10); - let metrics = contract.get_market_metrics(); - assert_eq!(metrics.average_price, 1000); - assert_eq!(metrics.total_volume, 5000); - assert_eq!(metrics.properties_listed, 10); - } - - #[ink::test] - fn add_market_trend_works() { - let mut contract = AnalyticsDashboard::new(); - let trend = MarketTrend { - period_start: 100, - period_end: 200, - price_change_percentage: 5, - volume_change_percentage: 10, - }; - contract.add_market_trend(trend.clone()); - let trends = contract.get_historical_trends(); - assert_eq!(trends.len(), 1); - assert_eq!(trends[0].price_change_percentage, 5); - } - - #[ink::test] - fn generate_market_report_works() { - let contract = AnalyticsDashboard::new(); - let report = contract.generate_market_report(); - assert_eq!(report.metrics.average_price, 0); - assert!(report.insights.contains("Gas optimization")); - } - } -} +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(unexpected_cfgs)] +#![allow(clippy::new_without_default)] + +use ink::prelude::string::String; +use ink::prelude::vec::Vec; + +#[ink::contract] +mod propchain_analytics { + use super::*; + + /// Market metrics representing aggregated property data. + #[derive( + Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct MarketMetrics { + pub average_price: u128, + pub total_volume: u128, + pub properties_listed: u64, + } + + /// Portfolio performance for an individual owner. + #[derive( + Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + #[allow(dead_code)] + pub struct PortfolioPerformance { + pub total_value: u128, + pub property_count: u64, + pub recent_transactions: u64, + } + + /// Trend analysis with historical data. + #[derive( + Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct MarketTrend { + pub period_start: u64, + pub period_end: u64, + pub price_change_percentage: i32, + pub volume_change_percentage: i32, + } + + /// User behavior analytics for a specific account. + #[derive( + Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + #[allow(dead_code)] + pub struct UserBehavior { + pub account: AccountId, + pub total_interactions: u64, + pub preferred_property_type: String, + pub risk_score: u8, + } + + /// Market Report. + #[derive( + Debug, Clone, PartialEq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + )] + #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] + pub struct MarketReport { + pub generated_at: u64, + pub metrics: MarketMetrics, + pub trend: MarketTrend, + pub insights: String, + } + + #[ink(storage)] + pub struct AnalyticsDashboard { + /// Administrator of the analytics dashboard + admin: AccountId, + /// Current market metrics + current_metrics: MarketMetrics, + /// Historical market trends + historical_trends: ink::storage::Mapping, + /// Trend count + trend_count: u64, + } + + impl AnalyticsDashboard { + #[ink(constructor)] + pub fn new() -> Self { + let caller = Self::env().caller(); + Self { + admin: caller, + current_metrics: MarketMetrics { + average_price: 0, + total_volume: 0, + properties_listed: 0, + }, + historical_trends: ink::storage::Mapping::default(), + trend_count: 0, + } + } + + /// Implement property market metrics calculation (average price, volume, etc.) + #[ink(message)] + pub fn get_market_metrics(&self) -> MarketMetrics { + self.current_metrics.clone() + } + + #[ink(message)] + pub fn update_market_metrics( + &mut self, + average_price: u128, + total_volume: u128, + properties_listed: u64, + ) { + self.ensure_admin(); + self.current_metrics = MarketMetrics { + average_price, + total_volume, + properties_listed, + }; + } + + /// Create market trend analysis with historical data + #[ink(message)] + pub fn add_market_trend(&mut self, trend: MarketTrend) { + self.ensure_admin(); + self.historical_trends.insert(self.trend_count, &trend); + self.trend_count += 1; + } + + #[ink(message)] + pub fn get_historical_trends(&self) -> Vec { + let mut trends = Vec::new(); + for i in 0..self.trend_count { + if let Some(trend) = self.historical_trends.get(i) { + trends.push(trend); + } + } + trends + } + + /// Create automated market reports generation + #[ink(message)] + pub fn generate_market_report(&self) -> MarketReport { + let latest_trend = if self.trend_count > 0 { + self.historical_trends + .get(self.trend_count - 1) + .unwrap_or(MarketTrend { + period_start: 0, + period_end: 0, + price_change_percentage: 0, + volume_change_percentage: 0, + }) + } else { + MarketTrend { + period_start: 0, + period_end: 0, + price_change_percentage: 0, + volume_change_percentage: 0, + } + }; + + MarketReport { + generated_at: self.env().block_timestamp(), + metrics: self.current_metrics.clone(), + trend: latest_trend, + insights: String::from( + "Market is relatively stable. Gas optimization is recommended.", + ), + } + } + + /// Add gas usage optimization recommendations + #[ink(message)] + pub fn get_gas_optimization_recommendations(&self) -> String { + String::from("Use batched operations and limit nested looping over dynamic collections (e.g. vectors). Store large items in Mappings instead of Vecs.") + } + + /// Ensure only the admin can modify metrics + fn ensure_admin(&self) { + assert_eq!( + self.env().caller(), + self.admin, + "Unauthorized: Analytics admin only" + ); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn market_metrics_defaults() { + let contract = AnalyticsDashboard::new(); + let metrics = contract.get_market_metrics(); + assert_eq!(metrics.average_price, 0); + assert_eq!(metrics.total_volume, 0); + assert_eq!(metrics.properties_listed, 0); + } + + #[ink::test] + fn update_market_metrics_works() { + let mut contract = AnalyticsDashboard::new(); + contract.update_market_metrics(1000, 5000, 10); + let metrics = contract.get_market_metrics(); + assert_eq!(metrics.average_price, 1000); + assert_eq!(metrics.total_volume, 5000); + assert_eq!(metrics.properties_listed, 10); + } + + #[ink::test] + fn add_market_trend_works() { + let mut contract = AnalyticsDashboard::new(); + let trend = MarketTrend { + period_start: 100, + period_end: 200, + price_change_percentage: 5, + volume_change_percentage: 10, + }; + contract.add_market_trend(trend.clone()); + let trends = contract.get_historical_trends(); + assert_eq!(trends.len(), 1); + assert_eq!(trends[0].price_change_percentage, 5); + } + + #[ink::test] + fn generate_market_report_works() { + let contract = AnalyticsDashboard::new(); + let report = contract.generate_market_report(); + assert_eq!(report.metrics.average_price, 0); + assert!(report.insights.contains("Gas optimization")); + } + } +} diff --git a/contracts/compliance_registry/lib.rs b/contracts/compliance_registry/lib.rs index db2ca1d..b8c30ee 100644 --- a/contracts/compliance_registry/lib.rs +++ b/contracts/compliance_registry/lib.rs @@ -190,7 +190,7 @@ mod compliance_registry { pub struct VerificationRequest { pub account: AccountId, pub jurisdiction: Jurisdiction, - pub document_hash: [u8; 32], // Hash of document for verification + pub document_hash: [u8; 32], // Hash of document for verification pub biometric_hash: [u8; 32], // Hash of biometric data pub request_timestamp: Timestamp, pub request_id: u64, @@ -388,7 +388,7 @@ mod compliance_registry { let mut verifiers = Mapping::default(); verifiers.insert(caller, &true); - let mut registry = Self { + let mut registry = Self { owner: caller, verifiers, compliance_data: Mapping::default(), @@ -504,15 +504,14 @@ mod compliance_registry { } // Check jurisdiction rules - let rules = self.jurisdiction_rules.get(jurisdiction) + let rules = self + .jurisdiction_rules + .get(jurisdiction) .ok_or(Error::JurisdictionNotSupported)?; // Validate minimum verification level - let verification_level = self.calculate_verification_level( - document_type, - biometric_method, - risk_score, - ); + let verification_level = + self.calculate_verification_level(document_type, biometric_method, risk_score); if verification_level < rules.minimum_verification_level { return Err(Error::NotVerified); } @@ -529,7 +528,7 @@ mod compliance_registry { verification_timestamp: now, expiry_timestamp: expiry, kyc_hash, - aml_checked: false, // Will be set separately + aml_checked: false, // Will be set separately sanctions_checked: false, // Will be set separately document_type, biometric_method, @@ -549,7 +548,7 @@ mod compliance_registry { }; self.compliance_data.insert(account, &compliance); - + // Log audit event self.log_audit_event(account, 0); // 0 = verification @@ -583,7 +582,7 @@ mod compliance_registry { // Biometric method contributes to level match biometric_method { - BiometricMethod::None => {}, + BiometricMethod::None => {} BiometricMethod::Fingerprint => level += 1, BiometricMethod::FaceRecognition => level += 1, BiometricMethod::VoiceRecognition => level += 1, @@ -671,7 +670,7 @@ mod compliance_registry { } self.compliance_data.insert(account, &data); - + // Log audit event self.log_audit_event(account, 1); // 1 = AML check @@ -699,7 +698,7 @@ mod compliance_registry { data.risk_level = RiskLevel::Prohibited; } self.compliance_data.insert(account, &data); - + // Log audit event self.log_audit_event(account, 2); // 2 = sanctions check @@ -717,7 +716,7 @@ mod compliance_registry { if let Some(mut data) = self.compliance_data.get(account) { data.status = VerificationStatus::Rejected; self.compliance_data.insert(account, &data); - + self.env().emit_event(VerificationUpdated { account, status: VerificationStatus::Rejected, @@ -856,7 +855,10 @@ mod compliance_registry { /// Get jurisdiction rules #[ink(message)] - pub fn get_jurisdiction_rules(&self, jurisdiction: Jurisdiction) -> Option { + pub fn get_jurisdiction_rules( + &self, + jurisdiction: Jurisdiction, + ) -> Option { self.jurisdiction_rules.get(jurisdiction) } @@ -870,7 +872,7 @@ mod compliance_registry { biometric_hash: [u8; 32], ) -> Result { let caller = self.env().caller(); - + // Check if there's already a pending request if let Some(existing_request_id) = self.account_requests.get(caller) { if let Some(request) = self.verification_requests.get(existing_request_id) { @@ -926,7 +928,9 @@ mod compliance_registry { ) -> Result<()> { self.ensure_verifier()?; - let request = self.verification_requests.get(request_id) + let request = self + .verification_requests + .get(request_id) .ok_or(Error::NotVerified)?; if request.status != VerificationStatus::Pending { @@ -948,7 +952,8 @@ mod compliance_registry { // Update request status let mut updated_request = request; updated_request.status = VerificationStatus::Verified; - self.verification_requests.insert(request_id, &updated_request); + self.verification_requests + .insert(request_id, &updated_request); } result @@ -971,7 +976,7 @@ mod compliance_registry { }; self.service_providers.insert(provider, &provider_info); - + // Also add as verifier if service type includes verification if service_type == 0 || service_type == 3 { self.verifiers.insert(provider, &true); @@ -1062,7 +1067,7 @@ mod compliance_registry { let now = self.env().block_timestamp(); let threshold_ms = (days_threshold as u64) * 24 * 60 * 60 * 1000; let expiry_threshold = data.expiry_timestamp.saturating_sub(threshold_ms); - + now >= expiry_threshold || data.status == VerificationStatus::Expired } else { true @@ -1072,10 +1077,7 @@ mod compliance_registry { /// Get accounts requiring re-verification (for automated monitoring) /// Note: Full implementation requires off-chain indexing #[ink(message)] - pub fn get_accounts_needing_reverification( - &self, - _limit: u32, - ) -> Vec { + pub fn get_accounts_needing_reverification(&self, _limit: u32) -> Vec { // This is a placeholder - full implementation would require // off-chain indexing or a different storage pattern // Off-chain services should maintain their own index of accounts @@ -1094,8 +1096,13 @@ mod compliance_registry { if !self.is_compliant(account) { return Err(Error::NotVerified); } - let data = self.compliance_data.get(account).ok_or(Error::NotVerified)?; - let rules = self.jurisdiction_rules.get(data.jurisdiction) + let data = self + .compliance_data + .get(account) + .ok_or(Error::NotVerified)?; + let rules = self + .jurisdiction_rules + .get(data.jurisdiction) .ok_or(Error::JurisdictionNotSupported)?; // Apply jurisdiction rules for operation @@ -1105,7 +1112,8 @@ mod compliance_registry { | ComplianceOperation::UpdateMetadata | ComplianceOperation::CreateEscrow | ComplianceOperation::ReleaseEscrow => { - if !rules.requires_kyc || !rules.requires_aml || !rules.requires_sanctions_check { + if !rules.requires_kyc || !rules.requires_aml || !rules.requires_sanctions_check + { return Ok(()); } if !data.aml_checked || !data.sanctions_checked { @@ -1132,7 +1140,8 @@ mod compliance_registry { let data = self.compliance_data.get(account)?; let audit_count = self.audit_log_count.get(account).unwrap_or(0); let last_audit = if audit_count > 0 { - self.audit_logs.get((account, audit_count - 1)) + self.audit_logs + .get((account, audit_count - 1)) .map(|l| l.timestamp) .unwrap_or(0) } else { @@ -1240,7 +1249,7 @@ mod compliance_registry { timestamp: self.env().block_timestamp(), }); } - + /// Set the ZK compliance contract address #[ink(message)] pub fn set_zk_compliance_contract(&mut self, zk_contract: AccountId) -> Result<()> { @@ -1269,7 +1278,7 @@ mod compliance_registry { // Since cross-contract calls in ink! are complex, we'll implement a simplified version // that assumes the zk-compliance contract has a method to check compliance // For now, we'll just verify that the account has valid ZK proofs for critical types - + // This is a simplified approach - in reality you'd make an actual cross-contract call // to the ZK compliance contract to verify compliance } @@ -1327,15 +1336,18 @@ mod compliance_registry { large_transaction_volume: false, source_of_funds_verified: true, }; - contract.update_aml_status(user, true, aml_factors) + contract + .update_aml_status(user, true, aml_factors) .expect("AML status update should succeed in test"); // Update sanctions status - contract.update_sanctions_status(user, true, SanctionsList::OFAC) + contract + .update_sanctions_status(user, true, SanctionsList::OFAC) .expect("Sanctions status update should succeed in test"); // Update consent (required for compliance) - contract.update_consent(user, ConsentStatus::Given) + contract + .update_consent(user, ConsentStatus::Given) .expect("Consent update should succeed in test"); // Check compliance @@ -1361,15 +1373,17 @@ mod compliance_registry { let kyc_hash = [0u8; 32]; // Verify user first - contract.submit_verification( - user, - Jurisdiction::US, - kyc_hash, - RiskLevel::Low, - DocumentType::Passport, - BiometricMethod::None, - 20, - ).expect("KYC verification should succeed in test"); + contract + .submit_verification( + user, + Jurisdiction::US, + kyc_hash, + RiskLevel::Low, + DocumentType::Passport, + BiometricMethod::None, + 20, + ) + .expect("KYC verification should succeed in test"); // Update AML with passing status let aml_factors = AMLRiskFactors { @@ -1379,11 +1393,14 @@ mod compliance_registry { large_transaction_volume: false, source_of_funds_verified: true, }; - contract.update_aml_status(user, true, aml_factors) + contract + .update_aml_status(user, true, aml_factors) .expect("AML status update should succeed in test"); - contract.update_sanctions_status(user, true, SanctionsList::UN) + contract + .update_sanctions_status(user, true, SanctionsList::UN) .expect("Sanctions status update should succeed in test"); - contract.update_consent(user, ConsentStatus::Given) + contract + .update_consent(user, ConsentStatus::Given) .expect("Consent update should succeed in test"); // User is compliant @@ -1397,7 +1414,8 @@ mod compliance_registry { large_transaction_volume: true, source_of_funds_verified: false, }; - contract.update_aml_status(user, false, high_risk_factors) + contract + .update_aml_status(user, false, high_risk_factors) .expect("AML status update should succeed in test"); // User is no longer compliant @@ -1410,16 +1428,17 @@ mod compliance_registry { let mut contract = ComplianceRegistry::new(); let user = AccountId::from([0x05; 32]); let kyc_hash = [0u8; 32]; - contract.submit_verification( - user, - Jurisdiction::US, - kyc_hash, - RiskLevel::Low, - DocumentType::Passport, - BiometricMethod::None, - 10, - ) - .expect("submit"); + contract + .submit_verification( + user, + Jurisdiction::US, + kyc_hash, + RiskLevel::Low, + DocumentType::Passport, + BiometricMethod::None, + 10, + ) + .expect("submit"); let aml = AMLRiskFactors { pep_status: false, high_risk_country: false, @@ -1428,11 +1447,19 @@ mod compliance_registry { source_of_funds_verified: true, }; contract.update_aml_status(user, true, aml).expect("aml"); - contract.update_sanctions_status(user, true, SanctionsList::OFAC).expect("sanctions"); - contract.update_consent(user, ConsentStatus::Given).expect("consent"); - - assert!(contract.check_transaction_compliance(user, ComplianceOperation::RegisterProperty).is_ok()); - assert!(contract.check_transaction_compliance(user, ComplianceOperation::TransferProperty).is_ok()); + contract + .update_sanctions_status(user, true, SanctionsList::OFAC) + .expect("sanctions"); + contract + .update_consent(user, ConsentStatus::Given) + .expect("consent"); + + assert!(contract + .check_transaction_compliance(user, ComplianceOperation::RegisterProperty) + .is_ok()); + assert!(contract + .check_transaction_compliance(user, ComplianceOperation::TransferProperty) + .is_ok()); } #[ink::test] @@ -1440,16 +1467,17 @@ mod compliance_registry { let mut contract = ComplianceRegistry::new(); let user = AccountId::from([0x06; 32]); let kyc_hash = [0u8; 32]; - contract.submit_verification( - user, - Jurisdiction::EU, - kyc_hash, - RiskLevel::Low, - DocumentType::NationalId, - BiometricMethod::None, - 5, - ) - .expect("submit"); + contract + .submit_verification( + user, + Jurisdiction::EU, + kyc_hash, + RiskLevel::Low, + DocumentType::NationalId, + BiometricMethod::None, + 5, + ) + .expect("submit"); let report = contract.get_compliance_report(user).expect("report"); assert_eq!(report.account, user); assert_eq!(report.jurisdiction, Jurisdiction::EU); @@ -1462,18 +1490,16 @@ mod compliance_registry { let request_id = contract .create_verification_request(Jurisdiction::UK, [1u8; 32], [2u8; 32]) .expect("create request"); - let status = contract.get_verification_workflow_status(request_id).expect("status"); + let status = contract + .get_verification_workflow_status(request_id) + .expect("status"); assert!(matches!(status, WorkflowStatus::Pending)); } #[ink::test] fn get_regulatory_report_works() { let contract = ComplianceRegistry::new(); - let report = contract.get_regulatory_report( - Jurisdiction::US, - 0, - 1000, - ); + let report = contract.get_regulatory_report(Jurisdiction::US, 0, 1000); assert_eq!(report.jurisdiction, Jurisdiction::US); assert_eq!(report.period_start, 0); assert_eq!(report.period_end, 1000); @@ -1486,4 +1512,4 @@ mod compliance_registry { assert!(!summary.lists_checked.is_empty()); } } -} \ No newline at end of file +} diff --git a/contracts/fees/src/lib.rs b/contracts/fees/src/lib.rs index fc960aa..e67022d 100644 --- a/contracts/fees/src/lib.rs +++ b/contracts/fees/src/lib.rs @@ -23,7 +23,10 @@ mod propchain_fees { const MAX_CONGESTION_MULTIPLIER: u32 = 300; // 300% of base #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct FeeConfig { /// Base fee per operation (in smallest unit) pub base_fee: u128, @@ -41,7 +44,10 @@ mod propchain_fees { /// Single data point for congestion/demand history (reserved for future analytics) #[derive(Debug, Clone, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] #[allow(dead_code)] pub struct FeeHistoryEntry { pub timestamp: u64, @@ -51,7 +57,10 @@ mod propchain_fees { /// Premium listing auction #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct PremiumAuction { pub property_id: u64, pub seller: AccountId, @@ -65,7 +74,10 @@ mod propchain_fees { /// Bid in a premium auction #[derive(Debug, Clone, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct AuctionBid { pub bidder: AccountId, pub amount: u128, @@ -74,7 +86,10 @@ mod propchain_fees { /// Reward record for validators/participants #[derive(Debug, Clone, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct RewardRecord { pub account: AccountId, pub amount: u128, @@ -83,7 +98,10 @@ mod propchain_fees { } #[derive(Debug, Clone, Copy, PartialEq, Eq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub enum RewardReason { ValidatorReward, LiquidityProvider, @@ -93,10 +111,13 @@ mod propchain_fees { /// Fee report for transparency and dashboard #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct FeeReport { pub config: FeeConfig, - pub congestion_index: u32, // 0-100 + pub congestion_index: u32, // 0-100 pub recommended_fee: u128, pub total_fees_collected: u128, pub total_distributed: u128, @@ -107,7 +128,10 @@ mod propchain_fees { /// Fee estimate for a user (optimization recommendation) #[derive(Debug, Clone, PartialEq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout))] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] pub struct FeeEstimate { pub operation: FeeOperation, pub estimated_fee: u128, @@ -242,11 +266,7 @@ mod propchain_fees { impl FeeManager { #[ink(constructor)] - pub fn new( - base_fee: u128, - min_fee: u128, - max_fee: u128, - ) -> Self { + pub fn new(base_fee: u128, min_fee: u128, max_fee: u128) -> Self { let caller = Self::env().caller(); let timestamp = Self::env().block_timestamp(); let default_config = FeeConfig { @@ -275,7 +295,7 @@ mod propchain_fees { validators: Mapping::default(), validator_list: Vec::new(), validator_share_bp: 5000, // 50% to validators - treasury_share_bp: 5000, // 50% to treasury + treasury_share_bp: 5000, // 50% to treasury } } @@ -288,7 +308,9 @@ mod propchain_fees { /// Get config for operation (operation-specific or default) fn get_config(&self, op: FeeOperation) -> FeeConfig { - self.operation_config.get(op).unwrap_or(self.default_config.clone()) + self.operation_config + .get(op) + .unwrap_or(self.default_config.clone()) } /// Compute current congestion index (0-100) from recent activity @@ -306,7 +328,10 @@ mod propchain_fees { /// Demand factor in basis points (from recent volume) fn demand_factor_bp(&self) -> u32 { let ci = self.congestion_index(); - self.default_config.demand_factor_bp.saturating_mul(ci).saturating_div(100) + self.default_config + .demand_factor_bp + .saturating_mul(ci) + .saturating_div(100) } // ========== Dynamic fee calculation ========== @@ -329,7 +354,10 @@ mod propchain_fees { from: AccountId, ) -> Result<(), FeeError> { let _ = from; - self.recent_ops_count = self.recent_ops_count.saturating_add(1).min(CONGESTION_WINDOW); + self.recent_ops_count = self + .recent_ops_count + .saturating_add(1) + .min(CONGESTION_WINDOW); let now = self.env().block_timestamp(); if now.saturating_sub(self.last_congestion_reset) > 3600 { self.last_congestion_reset = now; @@ -440,7 +468,10 @@ mod propchain_fees { pub fn place_bid(&mut self, auction_id: u64, amount: u128) -> Result<(), FeeError> { let caller = self.env().caller(); let now = self.env().block_timestamp(); - let mut auction = self.auctions.get(auction_id).ok_or(FeeError::AuctionNotFound)?; + let mut auction = self + .auctions + .get(auction_id) + .ok_or(FeeError::AuctionNotFound)?; if auction.settled { return Err(FeeError::AlreadySettled); } @@ -478,7 +509,10 @@ mod propchain_fees { #[ink(message)] pub fn settle_auction(&mut self, auction_id: u64) -> Result<(), FeeError> { let now = self.env().block_timestamp(); - let mut auction = self.auctions.get(auction_id).ok_or(FeeError::AuctionNotFound)?; + let mut auction = self + .auctions + .get(auction_id) + .ok_or(FeeError::AuctionNotFound)?; if auction.settled { return Err(FeeError::AlreadySettled); } @@ -563,7 +597,8 @@ mod propchain_fees { let per_validator = validator_total.saturating_div(validator_count as u128); for acc in validator_list { let current = self.pending_rewards.get(acc).unwrap_or(0); - self.pending_rewards.insert(acc, ¤t.saturating_add(per_validator)); + self.pending_rewards + .insert(acc, ¤t.saturating_add(per_validator)); self.record_reward(acc, per_validator, RewardReason::ValidatorReward); self.total_distributed = self.total_distributed.saturating_add(per_validator); self.env().emit_event(RewardsDistributed { diff --git a/contracts/fractional/src/lib.rs b/contracts/fractional/src/lib.rs index a4566fd..a216571 100644 --- a/contracts/fractional/src/lib.rs +++ b/contracts/fractional/src/lib.rs @@ -117,4 +117,3 @@ mod fractional { } } } - diff --git a/contracts/lib/src/lib.rs b/contracts/lib/src/lib.rs index edbbaa8..4b194b1 100644 --- a/contracts/lib/src/lib.rs +++ b/contracts/lib/src/lib.rs @@ -148,7 +148,13 @@ mod propchain_contracts { } #[derive( - Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, )] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct FractionalInfo { @@ -832,10 +838,7 @@ mod propchain_contracts { /// Set the fee manager contract address (admin only) #[ink(message)] - pub fn set_fee_manager( - &mut self, - fee_manager: Option, - ) -> Result<(), Error> { + pub fn set_fee_manager(&mut self, fee_manager: Option) -> Result<(), Error> { let caller = self.env().caller(); if caller != self.admin { return Err(Error::Unauthorized); @@ -2541,7 +2544,10 @@ mod propchain_contracts { total_shares: u128, ) -> Result<(), Error> { let caller = self.env().caller(); - let property = self.properties.get(property_id).ok_or(Error::PropertyNotFound)?; + let property = self + .properties + .get(property_id) + .ok_or(Error::PropertyNotFound)?; if caller != self.admin && caller != property.owner { return Err(Error::Unauthorized); } diff --git a/contracts/lib/src/tests.rs b/contracts/lib/src/tests.rs index 8919eb5..2698e07 100644 --- a/contracts/lib/src/tests.rs +++ b/contracts/lib/src/tests.rs @@ -1819,14 +1819,8 @@ mod tests { #[ink::test] fn test_get_dynamic_fee_without_manager_returns_zero() { let contract = PropertyRegistry::new(); - assert_eq!( - contract.get_dynamic_fee(FeeOperation::RegisterProperty), - 0 - ); - assert_eq!( - contract.get_dynamic_fee(FeeOperation::TransferProperty), - 0 - ); + assert_eq!(contract.get_dynamic_fee(FeeOperation::RegisterProperty), 0); + assert_eq!(contract.get_dynamic_fee(FeeOperation::TransferProperty), 0); } #[ink::test] @@ -1848,7 +1842,9 @@ mod tests { let accounts = default_accounts(); set_caller(accounts.alice); let mut contract = PropertyRegistry::new(); - contract.set_fee_manager(Some(AccountId::from([0x42; 32]))).unwrap(); + contract + .set_fee_manager(Some(AccountId::from([0x42; 32]))) + .unwrap(); assert!(contract.set_fee_manager(None).is_ok()); assert_eq!(contract.get_fee_manager(), None); } diff --git a/contracts/oracle/src/lib.rs b/contracts/oracle/src/lib.rs index 3f5e3d8..d1ef0b4 100644 --- a/contracts/oracle/src/lib.rs +++ b/contracts/oracle/src/lib.rs @@ -22,57 +22,57 @@ mod propchain_oracle { /// Property Valuation Oracle storage #[ink(storage)] pub struct PropertyValuationOracle { - /// Admin account - admin: AccountId, + /// Admin account + admin: AccountId, - /// Property valuations storage - pub property_valuations: Mapping, + /// Property valuations storage + pub property_valuations: Mapping, - /// Historical valuations per property - historical_valuations: Mapping>, + /// Historical valuations per property + historical_valuations: Mapping>, - /// Oracle sources configuration - oracle_sources: Mapping, + /// Oracle sources configuration + oracle_sources: Mapping, - /// Active oracle sources list - pub active_sources: Vec, + /// Active oracle sources list + pub active_sources: Vec, - /// Price alerts configuration - pub price_alerts: Mapping>, + /// Price alerts configuration + pub price_alerts: Mapping>, - /// Location-based adjustments - pub location_adjustments: Mapping, + /// Location-based adjustments + pub location_adjustments: Mapping, - /// Market trends data - pub market_trends: Mapping, + /// Market trends data + pub market_trends: Mapping, - /// Comparable properties cache - comparable_cache: Mapping>, + /// Comparable properties cache + comparable_cache: Mapping>, - /// Maximum staleness for price feeds (in seconds) - max_price_staleness: u64, + /// Maximum staleness for price feeds (in seconds) + max_price_staleness: u64, - /// Minimum sources required for valuation - pub min_sources_required: u32, + /// Minimum sources required for valuation + pub min_sources_required: u32, - /// Outlier detection threshold (standard deviations) - outlier_threshold: u32, + /// Outlier detection threshold (standard deviations) + outlier_threshold: u32, - /// Source reputations (0-1000, where 1000 is perfect) - pub source_reputations: Mapping, + /// Source reputations (0-1000, where 1000 is perfect) + pub source_reputations: Mapping, - /// Source stakes for slashing - pub source_stakes: Mapping, + /// Source stakes for slashing + pub source_stakes: Mapping, - /// Pending valuation requests: property_id -> timestamp - pub pending_requests: Mapping, + /// Pending valuation requests: property_id -> timestamp + pub pending_requests: Mapping, - /// Request counter for unique request IDs - pub request_id_counter: u64, + /// Request counter for unique request IDs + pub request_id_counter: u64, - /// AI valuation contract address - ai_valuation_contract: Option, - } + /// AI valuation contract address + ai_valuation_contract: Option, + } /// Events emitted by the oracle #[ink(event)] @@ -387,7 +387,10 @@ mod propchain_oracle { } /// Set AI valuation contract address #[ink(message)] - pub fn set_ai_valuation_contract(&mut self, ai_contract: AccountId) -> Result<(), OracleError> { + pub fn set_ai_valuation_contract( + &mut self, + ai_contract: AccountId, + ) -> Result<(), OracleError> { self.ensure_admin()?; self.ai_valuation_contract = Some(ai_contract); Ok(()) @@ -399,7 +402,6 @@ mod propchain_oracle { self.ai_valuation_contract } - /// Add oracle source (admin only) #[ink(message)] pub fn add_oracle_source(&mut self, source: OracleSource) -> Result<(), OracleError> { @@ -494,50 +496,50 @@ mod propchain_oracle { } fn get_price_from_source( - &self, - source: &OracleSource, - property_id: u64, - ) -> Result { - // This is a placeholder for actual price feed integration - // In production, this would call Chainlink, Pyth, or other oracles - match source.source_type { - OracleSourceType::Chainlink => { - // Implement Chainlink integration - Err(OracleError::PriceFeedError) - } - OracleSourceType::Pyth => { - // Implement Pyth integration - Err(OracleError::PriceFeedError) - } - OracleSourceType::Substrate => { - // Implement Substrate price feed integration (pallets/OCW) - Err(OracleError::PriceFeedError) - } - OracleSourceType::Manual => { - // Manual price updates only - Err(OracleError::PriceFeedError) - } - OracleSourceType::Custom => { - // Custom oracle logic - Err(OracleError::PriceFeedError) - } - OracleSourceType::AIModel => { - // AI model integration - call AI valuation contract - if let Some(_ai_contract) = self.ai_valuation_contract { - // In production, this would make a cross-contract call to AI valuation engine - // For now, return a mock price based on property_id - let mock_price = 500000u128 + (property_id as u128 * 1000); - Ok(PriceData { - price: mock_price, - timestamp: self.env().block_timestamp(), - source: source.id.clone(), - }) - } else { - Err(OracleError::PriceFeedError) - } - } + &self, + source: &OracleSource, + property_id: u64, + ) -> Result { + // This is a placeholder for actual price feed integration + // In production, this would call Chainlink, Pyth, or other oracles + match source.source_type { + OracleSourceType::Chainlink => { + // Implement Chainlink integration + Err(OracleError::PriceFeedError) + } + OracleSourceType::Pyth => { + // Implement Pyth integration + Err(OracleError::PriceFeedError) + } + OracleSourceType::Substrate => { + // Implement Substrate price feed integration (pallets/OCW) + Err(OracleError::PriceFeedError) + } + OracleSourceType::Manual => { + // Manual price updates only + Err(OracleError::PriceFeedError) + } + OracleSourceType::Custom => { + // Custom oracle logic + Err(OracleError::PriceFeedError) + } + OracleSourceType::AIModel => { + // AI model integration - call AI valuation contract + if let Some(_ai_contract) = self.ai_valuation_contract { + // In production, this would make a cross-contract call to AI valuation engine + // For now, return a mock price based on property_id + let mock_price = 500000u128 + (property_id as u128 * 1000); + Ok(PriceData { + price: mock_price, + timestamp: self.env().block_timestamp(), + source: source.id.clone(), + }) + } else { + Err(OracleError::PriceFeedError) } } + } + } fn is_price_fresh(&self, price_data: &PriceData) -> bool { let current_time = self.env().block_timestamp(); @@ -887,8 +889,8 @@ pub use propchain_traits::OracleError; mod oracle_tests { use super::*; // use ink::codegen::env::Env; // Removed invalid import - use ink::env::{test, DefaultEnvironment}; use crate::propchain_oracle::PropertyValuationOracle; + use ink::env::{test, DefaultEnvironment}; fn setup_oracle() -> PropertyValuationOracle { let accounts = test::default_accounts::(); diff --git a/contracts/property-token/src/lib.rs b/contracts/property-token/src/lib.rs index 5b95f1f..222d582 100644 --- a/contracts/property-token/src/lib.rs +++ b/contracts/property-token/src/lib.rs @@ -61,9 +61,11 @@ mod property_token { // Property-specific mappings token_properties: Mapping, property_tokens: Mapping, // property_id to token_id mapping - ownership_history: Mapping>, + ownership_history_count: Mapping, + ownership_history_items: Mapping<(TokenId, u32), OwnershipTransfer>, compliance_flags: Mapping, - legal_documents: Mapping>, + legal_documents_count: Mapping, + legal_documents_items: Mapping<(TokenId, u32), DocumentInfo>, // Cross-chain bridge mappings bridged_tokens: Mapping<(ChainId, TokenId), BridgedTokenInfo>, @@ -184,7 +186,13 @@ mod property_token { } #[derive( - Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, )] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct Proposal { @@ -199,7 +207,13 @@ mod property_token { } #[derive( - Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, )] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum ProposalStatus { @@ -210,7 +224,13 @@ mod property_token { } #[derive( - Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, )] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct Ask { @@ -222,7 +242,13 @@ mod property_token { } #[derive( - Debug, Clone, PartialEq, Eq, scale::Encode, scale::Decode, ink::storage::traits::StorageLayout, + Debug, + Clone, + PartialEq, + Eq, + scale::Encode, + scale::Decode, + ink::storage::traits::StorageLayout, )] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub struct TaxRecord { @@ -479,9 +505,11 @@ mod property_token { // Property-specific mappings token_properties: Mapping::default(), property_tokens: Mapping::default(), - ownership_history: Mapping::default(), + ownership_history_count: Mapping::default(), + ownership_history_items: Mapping::default(), compliance_flags: Mapping::default(), - legal_documents: Mapping::default(), + legal_documents_count: Mapping::default(), + legal_documents_items: Mapping::default(), // Cross-chain bridge mappings bridged_tokens: Mapping::default(), @@ -786,12 +814,17 @@ mod property_token { return Err(Error::Unauthorized); } let bal = self.balances.get((to, token_id)).unwrap_or(0); - self.balances.insert((to, token_id), &(bal.saturating_add(amount))); + self.balances + .insert((to, token_id), &(bal.saturating_add(amount))); let ts = self.total_shares.get(token_id).unwrap_or(0); self.total_shares .insert(token_id, &(ts.saturating_add(amount))); self.update_dividend_credit_on_change(to, token_id)?; - self.env().emit_event(SharesIssued { token_id, to, amount }); + self.env().emit_event(SharesIssued { + token_id, + to, + amount, + }); Ok(()) } @@ -893,11 +926,14 @@ mod property_token { self.dividend_balance.insert((caller, token_id), &0u128); match self.env().transfer(caller, owed) { Ok(_) => { - let mut rec = self.tax_records.get((caller, token_id)).unwrap_or(TaxRecord { - dividends_received: 0, - shares_sold: 0, - proceeds: 0, - }); + let mut rec = self + .tax_records + .get((caller, token_id)) + .unwrap_or(TaxRecord { + dividends_received: 0, + shares_sold: 0, + proceeds: 0, + }); rec.dividends_received = rec.dividends_received.saturating_add(owed); self.tax_records.insert((caller, token_id), &rec); self.env().emit_event(DividendsWithdrawn { @@ -959,7 +995,11 @@ mod property_token { return Err(Error::ProposalClosed); } let voter = self.env().caller(); - if self.votes_cast.get((token_id, proposal_id, voter)).unwrap_or(false) { + if self + .votes_cast + .get((token_id, proposal_id, voter)) + .unwrap_or(false) + { return Err(Error::Unauthorized); } let weight = self.balances.get((voter, token_id)).unwrap_or(0); @@ -969,7 +1009,8 @@ mod property_token { proposal.against_votes = proposal.against_votes.saturating_add(weight); } self.proposals.insert((token_id, proposal_id), &proposal); - self.votes_cast.insert((token_id, proposal_id, voter), &true); + self.votes_cast + .insert((token_id, proposal_id, voter), &true); self.env().emit_event(Voted { token_id, proposal_id, @@ -981,7 +1022,11 @@ mod property_token { } #[ink(message)] - pub fn execute_proposal(&mut self, token_id: TokenId, proposal_id: u64) -> Result { + pub fn execute_proposal( + &mut self, + token_id: TokenId, + proposal_id: u64, + ) -> Result { let mut proposal = self .proposals .get((token_id, proposal_id)) @@ -989,7 +1034,8 @@ mod property_token { if proposal.status != ProposalStatus::Open { return Err(Error::ProposalClosed); } - let passed = proposal.for_votes >= proposal.quorum && proposal.for_votes > proposal.against_votes; + let passed = proposal.for_votes >= proposal.quorum + && proposal.for_votes > proposal.against_votes; proposal.status = if passed { ProposalStatus::Executed } else { @@ -1044,7 +1090,10 @@ mod property_token { #[ink(message)] pub fn cancel_ask(&mut self, token_id: TokenId) -> Result<(), Error> { let seller = self.env().caller(); - let _ask = self.asks.get((token_id, seller)).ok_or(Error::AskNotFound)?; + let _ask = self + .asks + .get((token_id, seller)) + .ok_or(Error::AskNotFound)?; let esc = self.escrowed_shares.get((token_id, seller)).unwrap_or(0); let bal = self.balances.get((seller, token_id)).unwrap_or(0); self.balances @@ -1065,7 +1114,10 @@ mod property_token { if amount == 0 { return Err(Error::InvalidAmount); } - let ask = self.asks.get((token_id, seller)).ok_or(Error::AskNotFound)?; + let ask = self + .asks + .get((token_id, seller)) + .ok_or(Error::AskNotFound)?; if ask.amount < amount { return Err(Error::InvalidAmount); } @@ -1089,11 +1141,14 @@ mod property_token { .insert((token_id, seller), &(esc.saturating_sub(amount))); match self.env().transfer(seller, cost) { Ok(_) => { - let mut rec = self.tax_records.get((seller, token_id)).unwrap_or(TaxRecord { - dividends_received: 0, - shares_sold: 0, - proceeds: 0, - }); + let mut rec = self + .tax_records + .get((seller, token_id)) + .unwrap_or(TaxRecord { + dividends_received: 0, + shares_sold: 0, + proceeds: 0, + }); rec.shares_sold = rec.shares_sold.saturating_add(amount); rec.proceeds = rec.proceeds.saturating_add(cost); self.tax_records.insert((seller, token_id), &rec); @@ -1231,8 +1286,9 @@ mod property_token { }, }; - self.ownership_history - .insert(token_id, &vec![initial_transfer]); + self.ownership_history_count.insert(token_id, &1u32); + self.ownership_history_items + .insert((token_id, 0), &initial_transfer); // Initialize compliance as unverified let compliance_info = ComplianceInfo { @@ -1243,9 +1299,8 @@ mod property_token { }; self.compliance_flags.insert(token_id, &compliance_info); - // Initialize legal documents vector - self.legal_documents - .insert(token_id, &Vec::::new()); + // Initialize legal documents count + self.legal_documents_count.insert(token_id, &0u32); self.total_supply += 1; @@ -1258,6 +1313,69 @@ mod property_token { Ok(token_id) } + /// Property-specific: Batch registers properties in a single gas-efficient transaction + #[ink(message)] + pub fn batch_register_properties( + &mut self, + metadata_list: Vec, + ) -> Result, Error> { + let caller = self.env().caller(); + let mut issued_tokens = Vec::new(); + let current_time = self.env().block_timestamp(); + + for metadata in metadata_list { + self.token_counter += 1; + let token_id = self.token_counter; + + let property_info = PropertyInfo { + id: token_id, + owner: caller, + metadata: metadata.clone(), + registered_at: current_time, + }; + + self.token_owner.insert(token_id, &caller); + let balance = self.owner_token_count.get(caller).unwrap_or(0); + self.owner_token_count.insert(caller, &(balance + 1)); + + self.balances.insert((&caller, &token_id), &1u128); + self.token_properties.insert(token_id, &property_info); + self.property_tokens.insert(token_id, &token_id); + + let initial_transfer = OwnershipTransfer { + from: AccountId::from([0u8; 32]), + to: caller, + timestamp: current_time, + transaction_hash: Hash::default(), + }; + + self.ownership_history_count.insert(token_id, &1u32); + self.ownership_history_items + .insert((token_id, 0), &initial_transfer); + + let compliance_info = ComplianceInfo { + verified: false, + verification_date: 0, + verifier: AccountId::from([0u8; 32]), + compliance_type: String::from("KYC"), + }; + self.compliance_flags.insert(token_id, &compliance_info); + self.legal_documents_count.insert(token_id, &0u32); + + self.env().emit_event(PropertyTokenMinted { + token_id, + property_id: token_id, + owner: caller, + }); + + issued_tokens.push(token_id); + } + + self.total_supply += issued_tokens.len() as u64; + + Ok(issued_tokens) + } + /// Property-specific: Attaches a legal document to a token #[ink(message)] pub fn attach_legal_document( @@ -1273,8 +1391,8 @@ mod property_token { return Err(Error::Unauthorized); } - // Get existing documents - let mut documents = self.legal_documents.get(token_id).unwrap_or_default(); + // Get existing documents count + let document_count = self.legal_documents_count.get(token_id).unwrap_or(0); // Add new document let document_info = DocumentInfo { @@ -1284,10 +1402,11 @@ mod property_token { uploader: caller, }; - documents.push(document_info); - // Save updated documents - self.legal_documents.insert(token_id, &documents); + self.legal_documents_items + .insert((token_id, document_count), &document_info); + self.legal_documents_count + .insert(token_id, &(document_count + 1)); self.env().emit_event(LegalDocumentAttached { token_id, @@ -1334,7 +1453,17 @@ mod property_token { /// Property-specific: Gets ownership history for a token #[ink(message)] pub fn get_ownership_history(&self, token_id: TokenId) -> Option> { - self.ownership_history.get(token_id) + let count = self.ownership_history_count.get(token_id).unwrap_or(0); + if count == 0 { + return None; + } + let mut result = Vec::new(); + for i in 0..count { + if let Some(item) = self.ownership_history_items.get((token_id, i)) { + result.push(item); + } + } + Some(result) } /// Cross-chain: Initiates token bridging to another chain with multi-signature @@ -1635,8 +1764,9 @@ mod property_token { }, }; - self.ownership_history - .insert(new_token_id, &vec![initial_transfer]); + self.ownership_history_count.insert(new_token_id, &1u32); + self.ownership_history_items + .insert((new_token_id, 0), &initial_transfer); // Initialize compliance as verified for bridged tokens let compliance_info = ComplianceInfo { @@ -1647,9 +1777,8 @@ mod property_token { }; self.compliance_flags.insert(new_token_id, &compliance_info); - // Initialize legal documents vector - self.legal_documents - .insert(new_token_id, &Vec::::new()); + // Initialize legal documents count + self.legal_documents_count.insert(new_token_id, &0u32); self.total_supply += 1; @@ -2001,7 +2130,7 @@ mod property_token { from: AccountId, to: AccountId, ) -> Result<(), Error> { - let mut history = self.ownership_history.get(token_id).unwrap_or_default(); + let count = self.ownership_history_count.get(token_id).unwrap_or(0); let transfer_record = OwnershipTransfer { from, @@ -2018,9 +2147,9 @@ mod property_token { }, }; - history.push(transfer_record); - - self.ownership_history.insert(token_id, &history); + self.ownership_history_items + .insert((token_id, count), &transfer_record); + self.ownership_history_count.insert(token_id, &(count + 1)); Ok(()) } diff --git a/contracts/traits/src/lib.rs b/contracts/traits/src/lib.rs index a326767..922939e 100644 --- a/contracts/traits/src/lib.rs +++ b/contracts/traits/src/lib.rs @@ -125,10 +125,10 @@ pub struct PropertyValuation { derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) )] pub enum ValuationMethod { - Automated, // AVM (Automated Valuation Model) - Manual, // Manual appraisal - MarketData, // Based on market comparables - Hybrid, // Combination of methods + Automated, // AVM (Automated Valuation Model) + Manual, // Manual appraisal + MarketData, // Based on market comparables + Hybrid, // Combination of methods AIValuation, // AI-powered machine learning valuation } diff --git a/security-audit/src/main.rs b/security-audit/src/main.rs index 340862a..57542b9 100644 --- a/security-audit/src/main.rs +++ b/security-audit/src/main.rs @@ -183,21 +183,26 @@ fn main() -> Result<()> { for entry in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) { if entry.path().extension().is_some_and(|ext| ext == "rs") { let content = fs::read_to_string(entry.path()).unwrap_or_default(); - + // Simple heuristics for Gas Optimization - audit_report.gas_analysis.inefficient_loops += content.matches("for ").count() / 3; // Basic heuristic - audit_report.gas_analysis.storage_access_violations += content.matches("Mapping::").count() / 2; - audit_report.gas_analysis.large_allocations += content.matches("Vec::with_capacity").count(); + audit_report.gas_analysis.inefficient_loops += + content.matches("for ").count() / 3; // Basic heuristic + audit_report.gas_analysis.storage_access_violations += + content.matches("Mapping::").count() / 2; + audit_report.gas_analysis.large_allocations += + content.matches("Vec::with_capacity").count(); } } - // 5. Formal Verification & Fuzzing Info - println!("{}", "Checking Formal Verification & Fuzzing (heuristic)...".yellow()); + // 5. Formal Verification & Fuzzing Info + println!( + "{}", + "Checking Formal Verification & Fuzzing (heuristic)...".yellow() + ); // This is indicative metrics gathering for the report audit_report.formal_verification.cargo_contract_errors = 0; // Usually caught by actual PR checks - audit_report.formal_verification.slither_high_issues = 0; - audit_report.fuzzing.proptest_failures = 0; - + audit_report.formal_verification.slither_high_issues = 0; + audit_report.fuzzing.proptest_failures = 0; // Calculate Score // Calculate Score @@ -209,7 +214,7 @@ fn main() -> Result<()> { score = score.saturating_sub((audit_report.static_analysis.unsafe_blocks * 5) as u32); score = score.saturating_sub((audit_report.dependency_scan.vulnerabilities * 20) as u32); - score = score.saturating_sub((audit_report.gas_analysis.inefficient_loops * 1) as u32); + score = score.saturating_sub(audit_report.gas_analysis.inefficient_loops as u32); audit_report.score = score;