qudag_exchange_core/
config.rs

1//! Configuration management for QuDAG Exchange
2//!
3//! Provides unified configuration management with support for
4//! immutable deployment and dynamic fee model configuration.
5
6#[cfg(not(feature = "std"))]
7use alloc::{string::String, vec::Vec};
8
9use crate::{
10    fee_model::{AgentStatus, FeeModel, FeeModelParams},
11    immutable::{ImmutableDeployment, ImmutableStatus, LockableConfig},
12    payout::{FeeRouter, PayoutConfig},
13    types::{rUv, Timestamp},
14    Error, Result,
15};
16use serde::{Deserialize, Serialize};
17
18/// Main configuration for QuDAG Exchange
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ExchangeConfig {
21    /// Immutable deployment manager
22    pub immutable_deployment: ImmutableDeployment,
23
24    /// Dynamic fee model calculator
25    #[serde(skip)]
26    pub fee_model: Option<FeeModel>,
27
28    /// Network configuration
29    pub network: NetworkConfig,
30
31    /// Security configuration
32    pub security: SecurityConfig,
33
34    /// Business plan features configuration (optional)
35    pub business_plan: Option<BusinessPlanConfig>,
36
37    /// Fee router for automatic payouts (not serialized, recreated from config)
38    #[serde(skip)]
39    pub fee_router: Option<FeeRouter>,
40}
41
42/// Network configuration
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct NetworkConfig {
45    /// Chain ID for the network
46    pub chain_id: u64,
47
48    /// Network name
49    pub network_name: String,
50
51    /// Bootstrap peers for networking
52    pub bootstrap_peers: Vec<String>,
53
54    /// Listen address for P2P networking
55    pub listen_address: String,
56
57    /// Enable dark addressing features
58    pub enable_dark_addressing: bool,
59}
60
61impl Default for NetworkConfig {
62    fn default() -> Self {
63        Self {
64            chain_id: 1,
65            network_name: "qudag-exchange".to_string(),
66            bootstrap_peers: Vec::new(),
67            listen_address: "0.0.0.0:8080".to_string(),
68            enable_dark_addressing: true,
69        }
70    }
71}
72
73/// Security configuration
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SecurityConfig {
76    /// Require quantum-resistant signatures for all transactions
77    pub require_quantum_signatures: bool,
78
79    /// Minimum signature algorithm strength (e.g., "ML-DSA-87")
80    pub min_signature_strength: String,
81
82    /// Enable transaction replay protection
83    pub enable_replay_protection: bool,
84
85    /// Transaction expiry time in seconds
86    pub default_tx_expiry_seconds: u64,
87
88    /// Enable rate limiting
89    pub enable_rate_limiting: bool,
90
91    /// Maximum transactions per account per minute
92    pub max_tx_per_minute: u32,
93}
94
95impl Default for SecurityConfig {
96    fn default() -> Self {
97        Self {
98            require_quantum_signatures: true,
99            min_signature_strength: "ML-DSA-87".to_string(),
100            enable_replay_protection: true,
101            default_tx_expiry_seconds: 300, // 5 minutes
102            enable_rate_limiting: true,
103            max_tx_per_minute: 10,
104        }
105    }
106}
107
108impl ExchangeConfig {
109    /// Create a new exchange configuration with defaults
110    pub fn new() -> Result<Self> {
111        let mut config = Self {
112            immutable_deployment: ImmutableDeployment::new(),
113            fee_model: None,
114            network: NetworkConfig::default(),
115            security: SecurityConfig::default(),
116            business_plan: None, // Disabled by default
117            fee_router: None,
118        };
119
120        // Initialize fee model from immutable deployment params
121        config.initialize_fee_model()?;
122        Ok(config)
123    }
124
125    /// Create configuration from a lockable config
126    pub fn from_lockable_config(lockable: LockableConfig) -> Result<Self> {
127        let mut config = Self {
128            immutable_deployment: ImmutableDeployment::with_config(lockable.clone())?,
129            fee_model: None,
130            network: NetworkConfig {
131                chain_id: lockable.chain_id,
132                ..NetworkConfig::default()
133            },
134            security: SecurityConfig::default(),
135            business_plan: None, // Disabled by default
136            fee_router: None,
137        };
138
139        config.initialize_fee_model()?;
140        Ok(config)
141    }
142
143    /// Initialize the fee model from immutable deployment parameters
144    fn initialize_fee_model(&mut self) -> Result<()> {
145        let fee_params = self.immutable_deployment.system_config.fee_params.clone();
146        self.fee_model = Some(FeeModel::with_params(fee_params)?);
147        Ok(())
148    }
149
150    /// Update fee model parameters (respects immutable restrictions)
151    pub fn update_fee_params(
152        &mut self,
153        params: FeeModelParams,
154        current_time: Timestamp,
155    ) -> Result<()> {
156        // Update immutable deployment first (this enforces restrictions)
157        self.immutable_deployment
158            .update_fee_params(params.clone(), current_time)?;
159
160        // Update fee model
161        if let Some(ref mut fee_model) = self.fee_model {
162            fee_model.update_params(params)?;
163        } else {
164            self.fee_model = Some(FeeModel::with_params(params)?);
165        }
166
167        Ok(())
168    }
169
170    /// Calculate fee for a transaction
171    pub fn calculate_transaction_fee(
172        &self,
173        transaction_amount: rUv,
174        agent_status: &AgentStatus,
175        current_time: Timestamp,
176    ) -> Result<rUv> {
177        let fee_model = self
178            .fee_model
179            .as_ref()
180            .ok_or_else(|| Error::Other("Fee model not initialized".into()))?;
181
182        fee_model.calculate_fee_amount(transaction_amount, agent_status, current_time)
183    }
184
185    /// Get fee rate for an agent
186    pub fn get_fee_rate(&self, agent_status: &AgentStatus, current_time: Timestamp) -> Result<f64> {
187        let fee_model = self
188            .fee_model
189            .as_ref()
190            .ok_or_else(|| Error::Other("Fee model not initialized".into()))?;
191
192        fee_model.calculate_fee_rate(agent_status, current_time)
193    }
194
195    /// Enable immutable deployment mode
196    pub fn enable_immutable_mode(&mut self) -> Result<()> {
197        self.immutable_deployment.enable_immutable_mode()
198    }
199
200    /// Lock the system configuration (immutable deployment)
201    #[cfg(feature = "std")]
202    pub fn lock_system(
203        &mut self,
204        keypair: &qudag_crypto::MlDsaKeyPair,
205        current_time: Timestamp,
206    ) -> Result<()> {
207        self.immutable_deployment.lock_system(keypair, current_time)
208    }
209
210    /// Check if configuration can be modified
211    pub fn can_modify_config(&self, current_time: Timestamp) -> bool {
212        self.immutable_deployment.can_modify_config(current_time)
213    }
214
215    /// Get immutable deployment status
216    pub fn get_immutable_status(&self, current_time: Timestamp) -> ImmutableStatus {
217        self.immutable_deployment.get_status(current_time)
218    }
219
220    /// Update network configuration (respects immutable restrictions)
221    pub fn update_network_config(
222        &mut self,
223        network: NetworkConfig,
224        current_time: Timestamp,
225    ) -> Result<()> {
226        if !self.can_modify_config(current_time) {
227            return Err(Error::Other(
228                "Cannot modify network configuration: system is immutably locked".into(),
229            ));
230        }
231
232        // Update chain ID in lockable config too
233        self.immutable_deployment.system_config.chain_id = network.chain_id;
234        self.network = network;
235        Ok(())
236    }
237
238    /// Update security configuration (respects immutable restrictions)
239    pub fn update_security_config(
240        &mut self,
241        security: SecurityConfig,
242        current_time: Timestamp,
243    ) -> Result<()> {
244        if !self.can_modify_config(current_time) {
245            return Err(Error::Other(
246                "Cannot modify security configuration: system is immutably locked".into(),
247            ));
248        }
249
250        self.security = security;
251        Ok(())
252    }
253
254    /// Validate the entire configuration
255    pub fn validate(&self) -> Result<()> {
256        // Validate immutable deployment config
257        self.immutable_deployment.system_config.validate()?;
258
259        // Validate network config
260        if self.network.chain_id == 0 {
261            return Err(Error::Other("chain_id must be greater than 0".into()));
262        }
263
264        if self.network.network_name.is_empty() {
265            return Err(Error::Other("network_name cannot be empty".into()));
266        }
267
268        // Validate security config
269        if self.security.default_tx_expiry_seconds == 0 {
270            return Err(Error::Other(
271                "default_tx_expiry_seconds must be greater than 0".into(),
272            ));
273        }
274
275        if self.security.max_tx_per_minute == 0 {
276            return Err(Error::Other(
277                "max_tx_per_minute must be greater than 0".into(),
278            ));
279        }
280
281        Ok(())
282    }
283
284    /// Get configuration summary for display
285    pub fn get_summary(&self, current_time: Timestamp) -> ConfigSummary {
286        let immutable_status = self.get_immutable_status(current_time);
287        let fee_params = &self.immutable_deployment.system_config.fee_params;
288
289        let business_plan_summary = self.business_plan.as_ref().map(|bp| {
290            let total_contributors = self
291                .fee_router
292                .as_ref()
293                .map(|router| router.get_payout_history(None).len() as u32)
294                .unwrap_or(0);
295
296            BusinessPlanSummary {
297                enabled: bp.enabled,
298                auto_distribution_enabled: bp.enable_auto_distribution,
299                vault_management_enabled: bp.enable_vault_management,
300                role_earnings_enabled: bp.enable_role_earnings,
301                bounty_rewards_enabled: bp.enable_bounty_rewards,
302                total_contributors,
303                min_payout_threshold: bp.payout_config.min_payout_threshold.amount(),
304                system_fee_percentage: bp.payout_config.system_fee_percentage,
305            }
306        });
307
308        ConfigSummary {
309            network_name: self.network.network_name.clone(),
310            chain_id: self.network.chain_id,
311            immutable_status,
312            fee_model_summary: FeeModelSummary {
313                f_min: fee_params.f_min,
314                f_max: fee_params.f_max,
315                f_min_verified: fee_params.f_min_verified,
316                f_max_verified: fee_params.f_max_verified,
317                time_constant_days: fee_params.time_constant_seconds / (24 * 60 * 60),
318                usage_threshold: fee_params.usage_threshold_ruv,
319            },
320            security_enabled: self.security.require_quantum_signatures,
321            dark_addressing_enabled: self.network.enable_dark_addressing,
322            business_plan_summary,
323        }
324    }
325
326    /// Emergency governance override (unlock immutable system)
327    #[cfg(feature = "std")]
328    pub fn governance_override(
329        &mut self,
330        governance_keypair: &qudag_crypto::MlDsaKeyPair,
331        current_time: Timestamp,
332    ) -> Result<()> {
333        self.immutable_deployment
334            .governance_override(governance_keypair, current_time)
335    }
336
337    /// Enable business plan features
338    pub fn enable_business_plan(&mut self, config: BusinessPlanConfig) -> Result<()> {
339        // Validate business plan configuration
340        config.payout_config.validate()?;
341
342        // Initialize fee router if auto-distribution is enabled
343        if config.enable_auto_distribution {
344            self.fee_router = Some(FeeRouter::new(config.payout_config.clone()));
345        }
346
347        self.business_plan = Some(config);
348        Ok(())
349    }
350
351    /// Disable business plan features
352    pub fn disable_business_plan(&mut self) {
353        self.business_plan = None;
354        self.fee_router = None;
355    }
356
357    /// Check if business plan features are enabled
358    pub fn has_business_plan(&self) -> bool {
359        self.business_plan.as_ref().map_or(false, |bp| bp.enabled)
360    }
361
362    /// Get fee router if available
363    pub fn fee_router(&self) -> Option<&FeeRouter> {
364        self.fee_router.as_ref()
365    }
366
367    /// Get mutable fee router if available
368    pub fn fee_router_mut(&mut self) -> Option<&mut FeeRouter> {
369        self.fee_router.as_mut()
370    }
371
372    /// Update business plan configuration
373    pub fn update_business_plan(
374        &mut self,
375        config: BusinessPlanConfig,
376        current_time: Timestamp,
377    ) -> Result<()> {
378        if !self.can_modify_config(current_time) {
379            return Err(Error::Other(
380                "Cannot modify business plan configuration: system is immutably locked".into(),
381            ));
382        }
383
384        // Validate new configuration
385        config.payout_config.validate()?;
386
387        // Update fee router if needed
388        if config.enable_auto_distribution {
389            if let Some(ref mut fee_router) = self.fee_router {
390                fee_router.update_config(config.payout_config.clone())?;
391            } else {
392                self.fee_router = Some(FeeRouter::new(config.payout_config.clone()));
393            }
394        } else {
395            self.fee_router = None;
396        }
397
398        self.business_plan = Some(config);
399        Ok(())
400    }
401
402    /// Save configuration to bytes for persistence
403    pub fn to_bytes(&self) -> Result<Vec<u8>> {
404        bincode::serialize(self).map_err(|e| Error::SerializationError(e.to_string()))
405    }
406
407    /// Load configuration from bytes
408    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
409        let mut config: Self =
410            bincode::deserialize(bytes).map_err(|e| Error::SerializationError(e.to_string()))?;
411
412        // Re-initialize fee model since it's not serialized
413        config.initialize_fee_model()?;
414
415        // Re-initialize fee router if business plan is enabled
416        if let Some(ref business_plan) = config.business_plan {
417            if business_plan.enabled && business_plan.enable_auto_distribution {
418                config.fee_router = Some(FeeRouter::new(business_plan.payout_config.clone()));
419            }
420        }
421
422        config.validate()?;
423        Ok(config)
424    }
425}
426
427impl Default for ExchangeConfig {
428    fn default() -> Self {
429        Self::new().expect("Default configuration should be valid")
430    }
431}
432
433/// Business plan features configuration
434#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct BusinessPlanConfig {
436    /// Enable business plan features
437    pub enabled: bool,
438
439    /// Payout system configuration
440    pub payout_config: PayoutConfig,
441
442    /// Enable contributor vault management
443    pub enable_vault_management: bool,
444
445    /// Enable automatic fee distribution
446    pub enable_auto_distribution: bool,
447
448    /// Enable role-based earnings tracking
449    pub enable_role_earnings: bool,
450
451    /// Enable bounty agent rewards
452    pub enable_bounty_rewards: bool,
453
454    /// Governance settings
455    pub governance: GovernanceConfig,
456}
457
458impl Default for BusinessPlanConfig {
459    fn default() -> Self {
460        Self {
461            enabled: false, // Opt-in by default
462            payout_config: PayoutConfig::default(),
463            enable_vault_management: false,
464            enable_auto_distribution: false,
465            enable_role_earnings: false,
466            enable_bounty_rewards: false,
467            governance: GovernanceConfig::default(),
468        }
469    }
470}
471
472/// Governance configuration for business plan features
473#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct GovernanceConfig {
475    /// Allow contributors to override default payout percentages
476    pub allow_custom_percentages: bool,
477
478    /// Require governance approval for large payouts
479    pub require_approval_threshold: Option<rUv>,
480
481    /// Enable democratic voting on payout changes
482    pub enable_voting: bool,
483
484    /// Minimum voting period in seconds
485    pub min_voting_period_seconds: u64,
486}
487
488impl Default for GovernanceConfig {
489    fn default() -> Self {
490        Self {
491            allow_custom_percentages: true,
492            require_approval_threshold: Some(rUv::new(10000)), // 10k rUv
493            enable_voting: false,
494            min_voting_period_seconds: 7 * 24 * 60 * 60, // 7 days
495        }
496    }
497}
498
499/// Summary of configuration for display purposes
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct ConfigSummary {
502    pub network_name: String,
503    pub chain_id: u64,
504    pub immutable_status: ImmutableStatus,
505    pub fee_model_summary: FeeModelSummary,
506    pub security_enabled: bool,
507    pub dark_addressing_enabled: bool,
508    pub business_plan_summary: Option<BusinessPlanSummary>,
509}
510
511/// Business plan features summary for display
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct BusinessPlanSummary {
514    pub enabled: bool,
515    pub auto_distribution_enabled: bool,
516    pub vault_management_enabled: bool,
517    pub role_earnings_enabled: bool,
518    pub bounty_rewards_enabled: bool,
519    pub total_contributors: u32,
520    pub min_payout_threshold: u64,
521    pub system_fee_percentage: f64,
522}
523
524/// Fee model summary for display
525#[derive(Debug, Clone, Serialize, Deserialize)]
526pub struct FeeModelSummary {
527    pub f_min: f64,
528    pub f_max: f64,
529    pub f_min_verified: f64,
530    pub f_max_verified: f64,
531    pub time_constant_days: u64,
532    pub usage_threshold: u64,
533}
534
535/// Configuration builder for easier setup
536pub struct ExchangeConfigBuilder {
537    network: NetworkConfig,
538    security: SecurityConfig,
539    fee_params: FeeModelParams,
540    enable_immutable: bool,
541    business_plan: Option<BusinessPlanConfig>,
542}
543
544impl ExchangeConfigBuilder {
545    /// Create a new configuration builder
546    pub fn new() -> Self {
547        Self {
548            network: NetworkConfig::default(),
549            security: SecurityConfig::default(),
550            fee_params: FeeModelParams::default(),
551            enable_immutable: false,
552            business_plan: None,
553        }
554    }
555
556    /// Set network configuration
557    pub fn with_network(mut self, network: NetworkConfig) -> Self {
558        self.network = network;
559        self
560    }
561
562    /// Set security configuration
563    pub fn with_security(mut self, security: SecurityConfig) -> Self {
564        self.security = security;
565        self
566    }
567
568    /// Set fee model parameters
569    pub fn with_fee_params(mut self, fee_params: FeeModelParams) -> Self {
570        self.fee_params = fee_params;
571        self
572    }
573
574    /// Enable immutable deployment mode
575    pub fn with_immutable_mode(mut self) -> Self {
576        self.enable_immutable = true;
577        self
578    }
579
580    /// Set chain ID
581    pub fn with_chain_id(mut self, chain_id: u64) -> Self {
582        self.network.chain_id = chain_id;
583        self
584    }
585
586    /// Set network name
587    pub fn with_network_name(mut self, name: impl Into<String>) -> Self {
588        self.network.network_name = name.into();
589        self
590    }
591
592    /// Enable business plan features
593    pub fn with_business_plan(mut self, business_plan: BusinessPlanConfig) -> Self {
594        self.business_plan = Some(business_plan);
595        self
596    }
597
598    /// Enable basic business plan features with default configuration
599    pub fn with_basic_business_plan(mut self) -> Self {
600        let mut bp_config = BusinessPlanConfig::default();
601        bp_config.enabled = true;
602        bp_config.enable_auto_distribution = true;
603        bp_config.enable_role_earnings = true;
604        self.business_plan = Some(bp_config);
605        self
606    }
607
608    /// Build the configuration
609    pub fn build(self) -> Result<ExchangeConfig> {
610        let lockable_config = LockableConfig {
611            fee_params: self.fee_params,
612            chain_id: self.network.chain_id,
613            ..LockableConfig::default()
614        };
615
616        let mut config = ExchangeConfig::from_lockable_config(lockable_config)?;
617        config.network = self.network;
618        config.security = self.security;
619
620        if self.enable_immutable {
621            config.enable_immutable_mode()?;
622        }
623
624        // Enable business plan if configured
625        if let Some(business_plan) = self.business_plan {
626            config.enable_business_plan(business_plan)?;
627        }
628
629        config.validate()?;
630        Ok(config)
631    }
632}
633
634impl Default for ExchangeConfigBuilder {
635    fn default() -> Self {
636        Self::new()
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643
644    #[test]
645    fn test_config_creation() {
646        let config = ExchangeConfig::new().unwrap();
647        assert!(config.fee_model.is_some());
648        assert_eq!(config.network.chain_id, 1);
649        assert_eq!(config.network.network_name, "qudag-exchange");
650        config.validate().unwrap();
651    }
652
653    #[test]
654    fn test_config_builder() {
655        let config = ExchangeConfigBuilder::new()
656            .with_chain_id(42)
657            .with_network_name("test-network")
658            .with_immutable_mode()
659            .build()
660            .unwrap();
661
662        assert_eq!(config.network.chain_id, 42);
663        assert_eq!(config.network.network_name, "test-network");
664        assert!(config.immutable_deployment.config.enabled);
665    }
666
667    #[test]
668    fn test_fee_calculation() {
669        let config = ExchangeConfig::new().unwrap();
670        let agent = AgentStatus::new_unverified(Timestamp::new(0));
671        let current_time = Timestamp::new(1000);
672
673        let fee = config
674            .calculate_transaction_fee(rUv::new(1000), &agent, current_time)
675            .unwrap();
676
677        // Should be minimum fee for new unverified agent
678        assert_eq!(fee.amount(), 1); // 1000 * 0.001 = 1
679
680        let rate = config.get_fee_rate(&agent, current_time).unwrap();
681        assert!((rate - 0.001).abs() < 1e-10);
682    }
683
684    #[test]
685    fn test_config_modification_restrictions() {
686        let mut config = ExchangeConfig::new().unwrap();
687        config.enable_immutable_mode().unwrap();
688
689        let current_time = Timestamp::new(1000);
690
691        // Should be able to modify before locking
692        assert!(config.can_modify_config(current_time));
693
694        let new_params = FeeModelParams::default();
695        config.update_fee_params(new_params, current_time).unwrap();
696
697        // Simulate locked state
698        config.immutable_deployment.config.locked_at = Some(current_time);
699        config.immutable_deployment.config.lock_signature =
700            Some(crate::immutable::ImmutableSignature {
701                algorithm: "ML-DSA-87".to_string(),
702                public_key: vec![1, 2, 3],
703                signature: vec![4, 5, 6],
704                config_hash: crate::types::Hash::from_bytes([0u8; 32]),
705            });
706
707        // Should not be able to modify after grace period
708        let post_grace = Timestamp::new(current_time.value() + 25 * 60 * 60);
709        assert!(!config.can_modify_config(post_grace));
710
711        let result = config.update_fee_params(FeeModelParams::default(), post_grace);
712        assert!(result.is_err());
713    }
714
715    #[test]
716    fn test_config_summary() {
717        let config = ExchangeConfig::new().unwrap();
718        let current_time = Timestamp::new(1000);
719
720        let summary = config.get_summary(current_time);
721        assert_eq!(summary.network_name, "qudag-exchange");
722        assert_eq!(summary.chain_id, 1);
723        assert!(!summary.immutable_status.enabled);
724        assert_eq!(summary.fee_model_summary.f_min, 0.001);
725    }
726
727    #[test]
728    fn test_config_serialization() {
729        let config = ExchangeConfig::new().unwrap();
730
731        let bytes = config.to_bytes().unwrap();
732        let restored = ExchangeConfig::from_bytes(&bytes).unwrap();
733
734        // Fee model should be restored
735        assert!(restored.fee_model.is_some());
736        assert_eq!(config.network.chain_id, restored.network.chain_id);
737        assert_eq!(config.network.network_name, restored.network.network_name);
738    }
739
740    #[test]
741    fn test_network_config_validation() {
742        let mut config = ExchangeConfig::new().unwrap();
743
744        // Valid config should pass
745        config.validate().unwrap();
746
747        // Invalid chain ID should fail
748        config.network.chain_id = 0;
749        assert!(config.validate().is_err());
750
751        // Empty network name should fail
752        config.network.chain_id = 1;
753        config.network.network_name = String::new();
754        assert!(config.validate().is_err());
755    }
756}