diff --git a/contracts/compliance_registry/lib.rs b/contracts/compliance_registry/lib.rs index a9e4b4ce..6cda1683 100644 --- a/contracts/compliance_registry/lib.rs +++ b/contracts/compliance_registry/lib.rs @@ -44,6 +44,52 @@ mod compliance_registry { Other, } + /// Supported operations that can be enabled/disabled per jurisdiction + #[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] + pub enum AllowedOperation { + Transfer, + ListForSale, + Purchase, + CreateEscrow, + ReleaseEscrow, + BridgeTransfer, + UpdateMetadata, + RegisterProperty, + } + + /// Matrix of allowed operations for a jurisdiction + #[derive(Debug, Clone, Copy, scale::Encode, scale::Decode, Default)] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] + pub struct OperationsMatrix { + pub transfer: bool, + pub list_for_sale: bool, + pub purchase: bool, + pub create_escrow: bool, + pub release_escrow: bool, + pub bridge_transfer: bool, + pub update_metadata: bool, + pub register_property: bool, + } + + /// Token jurisdiction configuration + #[derive(Debug, Clone, Copy, scale::Encode, scale::Decode)] + #[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) + )] + pub struct TokenJurisdictionConfig { + pub jurisdiction: Jurisdiction, + pub operations: OperationsMatrix, + pub is_active: bool, + } + /// Risk level assessment #[derive(Debug, PartialEq, Eq, Clone, Copy, scale::Encode, scale::Decode)] #[cfg_attr( @@ -276,6 +322,10 @@ mod compliance_registry { screening_cache: Mapping, /// TTL for cached screening results in seconds screening_cache_ttl: u64, + /// Per-token jurisdiction configuration: tokenId -> TokenJurisdictionConfig + token_jurisdictions: Mapping, + /// Operations matrix for each jurisdiction (default rules) + jurisdiction_operations: Mapping, } /// Errors @@ -306,6 +356,12 @@ mod compliance_registry { JurisdictionNotSupported, /// Sanctions check failed SanctionsCheckFailed, + /// Operation not allowed for this token's jurisdiction + OperationNotAllowed, + /// Token not found in jurisdiction registry + TokenNotFound, + /// Invalid operation specified + InvalidOperation, } impl core::fmt::Display for Error { @@ -397,6 +453,15 @@ mod compliance_registry { Error::SanctionsCheckFailed => { "The account has failed sanctions screening" } + Error::OperationNotAllowed => { + "The requested operation is not allowed for this token's jurisdiction" + } + Error::TokenNotFound => { + "The specified token ID was not found in the jurisdiction registry" + } + Error::InvalidOperation => { + "The specified operation is invalid or not recognized" + } } } @@ -507,6 +572,22 @@ mod compliance_registry { timestamp: u64, } + #[ink(event)] + pub struct TokenJurisdictionUpdated { + #[ink(topic)] + token_id: u64, + jurisdiction: Jurisdiction, + is_active: bool, + timestamp: Timestamp, + } + + #[ink(event)] + pub struct JurisdictionOperationsUpdated { + #[ink(topic)] + jurisdiction: Jurisdiction, + timestamp: Timestamp, + } + /// Compliance report for an account (audit trail and reporting - Issue #45) #[derive(Debug, Clone, scale::Encode, scale::Decode)] #[cfg_attr( @@ -655,6 +736,8 @@ mod compliance_registry { // Initialize default jurisdiction rules registry.init_default_jurisdiction_rules(); + // Initialize default jurisdiction operations matrices + registry.init_default_jurisdiction_operations(); registry } @@ -726,6 +809,99 @@ mod compliance_registry { ); } + /// Initialize default operation matrices for each jurisdiction + fn init_default_jurisdiction_operations(&mut self) { + // US: Most operations allowed except bridge transfers by default + self.jurisdiction_operations.insert( + &Jurisdiction::US, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: false, // Restrict cross-chain in US + update_metadata: true, + register_property: true, + }, + ); + + // EU: All operations allowed with full compliance + self.jurisdiction_operations.insert( + &Jurisdiction::EU, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: true, + update_metadata: true, + register_property: true, + }, + ); + + // UK: Similar to EU + self.jurisdiction_operations.insert( + &Jurisdiction::UK, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: true, + update_metadata: true, + register_property: true, + }, + ); + + // Singapore: Bridge transfers restricted but others allowed + self.jurisdiction_operations.insert( + &Jurisdiction::Singapore, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: false, // MAS regulations restrict cross-chain + update_metadata: true, + register_property: true, + }, + ); + + // UAE: Similar to Singapore + self.jurisdiction_operations.insert( + &Jurisdiction::UAE, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: false, + update_metadata: true, + register_property: true, + }, + ); + + // Other jurisdictions: Conservative defaults + self.jurisdiction_operations.insert( + &Jurisdiction::Other, + &OperationsMatrix { + transfer: true, + list_for_sale: true, + purchase: true, + create_escrow: true, + release_escrow: true, + bridge_transfer: false, + update_metadata: true, + register_property: true, + }, + ); + } + /// Add authorized verifier (KYC service) #[ink(message)] pub fn add_verifier(&mut self, verifier: AccountId) -> Result<()> { @@ -1449,6 +1625,158 @@ mod compliance_registry { Ok(()) } + // ========== Jurisdiction-aware feature flags per tokenId ========== + + /// Helper to convert AllowedOperation to the corresponding boolean in OperationsMatrix + fn get_operation_flag(matrix: &OperationsMatrix, operation: AllowedOperation) -> Result { + match operation { + AllowedOperation::Transfer => Ok(matrix.transfer), + AllowedOperation::ListForSale => Ok(matrix.list_for_sale), + AllowedOperation::Purchase => Ok(matrix.purchase), + AllowedOperation::CreateEscrow => Ok(matrix.create_escrow), + AllowedOperation::ReleaseEscrow => Ok(matrix.release_escrow), + AllowedOperation::BridgeTransfer => Ok(matrix.bridge_transfer), + AllowedOperation::UpdateMetadata => Ok(matrix.update_metadata), + AllowedOperation::RegisterProperty => Ok(matrix.register_property), + } + } + + /// Set or update jurisdiction configuration for a token (admin only) + #[ink(message)] + pub fn set_token_jurisdiction( + &mut self, + token_id: u64, + jurisdiction: Jurisdiction, + ) -> Result<()> { + self.ensure_owner()?; + + // Get default operations matrix for this jurisdiction + let operations = self.jurisdiction_operations.get(jurisdiction) + .ok_or(Error::JurisdictionNotSupported)?; + + let config = TokenJurisdictionConfig { + jurisdiction, + operations, + is_active: true, + }; + + self.token_jurisdictions.insert(token_id, &config); + + self.env().emit_event(TokenJurisdictionUpdated { + token_id, + jurisdiction, + is_active: true, + timestamp: self.env().block_timestamp(), + }); + + Ok(()) + } + + /// Update operations matrix for a jurisdiction (admin only) + #[ink(message)] + pub fn set_jurisdiction_operations( + &mut self, + jurisdiction: Jurisdiction, + operations: OperationsMatrix, + ) -> Result<()> { + self.ensure_owner()?; + + self.jurisdiction_operations.insert(jurisdiction, &operations); + + self.env().emit_event(JurisdictionOperationsUpdated { + jurisdiction, + timestamp: self.env().block_timestamp(), + }); + + Ok(()) + } + + /// Check if a specific operation is allowed for a token + #[ink(message)] + pub fn is_operation_allowed( + &self, + token_id: u64, + operation: AllowedOperation, + ) -> Result { + let config = self.token_jurisdictions.get(token_id) + .ok_or(Error::TokenNotFound)?; + + if !config.is_active { + return Ok(false); + } + + Self::get_operation_flag(&config.operations, operation) + } + + /// Get the full allowed operations matrix for a token (for marketplace consumers) + #[ink(message)] + pub fn get_token_operations_matrix( + &self, + token_id: u64, + ) -> Result { + let config = self.token_jurisdictions.get(token_id) + .ok_or(Error::TokenNotFound)?; + Ok(config.operations) + } + + /// Get token's jurisdiction configuration + #[ink(message)] + pub fn get_token_jurisdiction( + &self, + token_id: u64, + ) -> Result { + self.token_jurisdictions.get(token_id) + .ok_or(Error::TokenNotFound) + } + + /// Deactivate a token's jurisdiction configuration (disable all operations) + #[ink(message)] + pub fn deactivate_token( + &mut self, + token_id: u64, + ) -> Result<()> { + self.ensure_owner()?; + + let mut config = self.token_jurisdictions.get(token_id) + .ok_or(Error::TokenNotFound)?; + + config.is_active = false; + self.token_jurisdictions.insert(token_id, &config); + + self.env().emit_event(TokenJurisdictionUpdated { + token_id, + jurisdiction: config.jurisdiction, + is_active: false, + timestamp: self.env().block_timestamp(), + }); + + Ok(()) + } + + /// Reactivate a previously deactivated token + #[ink(message)] + pub fn reactivate_token( + &mut self, + token_id: u64, + ) -> Result<()> { + self.ensure_owner()?; + + let mut config = self.token_jurisdictions.get(token_id) + .ok_or(Error::TokenNotFound)?; + + config.is_active = true; + self.token_jurisdictions.insert(token_id, &config); + + self.env().emit_event(TokenJurisdictionUpdated { + token_id, + jurisdiction: config.jurisdiction, + is_active: true, + timestamp: self.env().block_timestamp(), + }); + + Ok(()) + } + /// Compliance reporting and audit trail: full report for an account #[ink(message)] pub fn get_compliance_report(&self, account: AccountId) -> Option { @@ -2421,4 +2749,4 @@ mod compliance_registry { assert_eq!(result, Err(Error::NotAuthorized)); } } -} +} \ No newline at end of file diff --git a/contracts/factory/DEPLOYMENT_GUIDE.md b/contracts/factory/DEPLOYMENT_GUIDE.md index 0d2c7314..9cfb0dac 100644 --- a/contracts/factory/DEPLOYMENT_GUIDE.md +++ b/contracts/factory/DEPLOYMENT_GUIDE.md @@ -246,6 +246,130 @@ let address = factory.deploy_contract(config, "2.0.0".to_string())?; - Upload contract code first - Set code hash using `set_code_hash` +## Pre-Deployment Checklist (Production) + +Complete all items in this checklist before deploying the factory to mainnet: + +### ๐Ÿ“‹ Smart Contract Audits +- [ ] All factory contract dependencies have been audited by a reputable third party +- [ ] Factory code itself has undergone a full security audit with all findings resolved +- [ ] All template contracts (PropertyToken, Escrow, Oracle) have been independently audited +- [ ] Audit reports are publicly disclosed and published in the repository +- [ ] Reentrancy guards, access controls, and input validation have been verified by auditors + +### ๐Ÿ” Multisig & Access Control Configuration +- [ ] Factory admin is set to a 2/3 or 3/5 multisig wallet +- [ ] Multisig signers are distributed geographically and organizationally +- [ ] Timelock is configured for all admin operations (minimum 48-hour delay) +- [ ] Code hash update permissions are restricted to the multisig only +- [ ] Emergency pause functionality is tested and operational +- [ ] Admin key rotation process is documented and agreed upon by all signers + +### ๐Ÿ”ฎ Oracle Setup +- [ ] Price oracle nodes are deployed and synced with major data providers +- [ ] Oracle update intervals are set according to asset volatility requirements +- [ ] Oracle dispute mechanisms are tested and operational +- [ ] Fallback oracles are configured for redundancy +- [ ] Minimum required oracle signatures are set to prevent single points of failure +- [ ] Historical price data integrity has been verified + +### ๐Ÿ›๏ธ Governance Configuration +- [ ] Proposal threshold is set to the required token supply percentage +- [ ] Voting period duration is appropriate for the community size +- [ ] Execution delay is configured to allow for review periods +- [ ] Quorum requirements are tested and validated +- [ ] All governance parameters are documented and communicated to stakeholders +- [ ] Emergency governance procedures are in place + +## Post-Deployment Verification Script + +After deploying to production, run this comprehensive verification script to confirm everything is configured correctly: + +```bash +#!/bin/bash +set -euo pipefail + +# Configuration +FACTORY_ADDRESS="YOUR_FACTORY_ADDRESS" +RPC_URL="YOUR_RPC_ENDPOINT" +REQUIRED_CODE_HASHES=( + "PropertyToken:0xabc123..." + "Escrow:0xdef456..." + "Oracle:0xghi789..." +) + +echo "๐Ÿ” Starting post-deployment verification for PropChain Factory" +echo "Factory Address: $FACTORY_ADDRESS" +echo "RPC URL: $RPC_URL" +echo "========================================" + +# 1. Verify factory deployment exists +echo -e "\n๐Ÿ“ฆ Step 1: Verifying factory deployment exists..." +cargo contract call \ + --contract $FACTORY_ADDRESS \ + --message get_deployment_count \ + --url $RPC_URL +echo "โœ… Factory is deployed and responsive" + +# 2. Verify all code hashes are correctly set +echo -e "\n๐Ÿ”‘ Step 2: Verifying all contract code hashes..." +for entry in "${REQUIRED_CODE_HASHES[@]}"; do + IFS=':' read -r contract_type expected_hash <<< "$entry" + stored_hash=$(cargo contract call \ + --contract $FACTORY_ADDRESS \ + --message get_code_hash \ + --args $contract_type \ + --url $RPC_URL | grep -o '0x[0-9a-f]*') + + if [ "$stored_hash" = "$expected_hash" ]; then + echo " โœ… $contract_type: Code hash matches" + else + echo " โŒ $contract_type: Code hash mismatch! Expected $expected_hash, got $stored_hash" + exit 1 + fi +done + +# 3. Verify admin configuration +echo -e "\n๐Ÿ‘ค Step 3: Verifying admin configuration..." +admin_address=$(cargo contract call \ + --contract $FACTORY_ADDRESS \ + --message get_admin \ + --url $RPC_URL | grep -o '5[0-9a-zA-Z]*') + +# Check if admin is the expected multisig address +EXPECTED_MULTISIG="YOUR_MULTISIG_ADDRESS" +if [ "$admin_address" = "$EXPECTED_MULTISIG" ]; then + echo " โœ… Admin is correctly set to multisig: $admin_address" +else + echo " โš ๏ธ Admin address ($admin_address) does not match expected multisig" +fi + +# 4. Test contract deployment workflow +echo -e "\n๐Ÿงช Step 4: Testing contract deployment workflow..." +echo " Deploying a test PropertyToken to verify factory functionality..." +# This runs a test deployment to ensure the factory can create contracts successfully +cargo contract call \ + --contract $FACTORY_ADDRESS \ + --message deploy_contract \ + --args '{"contract_type":"PropertyToken","salt":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","init_params":"0x..."}' "1.0.0" \ + --url $RPC_URL \ + --suri //TestDeployer +echo " โœ… Test deployment completed successfully" + +# 5. Verify event emission +echo -e "\n๐Ÿ“ก Step 5: Verifying event emission..." +cargo contract events --url $RPC_URL --contract $FACTORY_ADDRESS --limit 10 | grep -E "ContractDeployed|CodeHashUpdated" +echo "โœ… Factory events are being emitted correctly" + +echo -e "\n๐ŸŽ‰ All post-deployment checks passed! Factory is ready for production use." +``` + +### Script Usage +1. Save the script as `verify-deployment.sh` +2. Update the configuration variables at the top +3. Make it executable: `chmod +x verify-deployment.sh` +4. Run it: `./verify-deployment.sh` + ## Security Considerations 1. **Admin Security**: Protect admin private keys