//! 升级提案模块 use crate::{migration::UpgradeData, version::Version}; use serde::{Deserialize, Serialize}; use std::fmt; /// 提案ID #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ProposalId(pub String); impl ProposalId { pub fn new(id: String) -> Self { Self(id) } pub fn generate(module_name: &str, target_version: &Version) -> Self { let id = format!("{}_{}", module_name, target_version); Self(id) } } impl fmt::Display for ProposalId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// 提案状态 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ProposalStatus { /// 待处理 Pending, /// 投票中 Voting, /// 已批准 Approved, /// 已拒绝 Rejected, /// 已执行 Executed, /// 执行失败 Failed, /// 已取消 Cancelled, } impl fmt::Display for ProposalStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ProposalStatus::Pending => write!(f, "Pending"), ProposalStatus::Voting => write!(f, "Voting"), ProposalStatus::Approved => write!(f, "Approved"), ProposalStatus::Rejected => write!(f, "Rejected"), ProposalStatus::Executed => write!(f, "Executed"), ProposalStatus::Failed => write!(f, "Failed"), ProposalStatus::Cancelled => write!(f, "Cancelled"), } } } /// 升级提案 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpgradeProposal { /// 提案ID pub proposal_id: ProposalId, /// 模块名称 pub module_name: String, /// 当前版本 pub current_version: Version, /// 目标版本 pub target_version: Version, /// 提案描述 pub description: String, /// 升级数据 pub upgrade_data: UpgradeData, /// 提案者地址 pub proposer: String, /// 创建时间 pub created_at: chrono::DateTime, /// 投票截止时间 pub voting_deadline: chrono::DateTime, /// 执行时间(可选,批准后设置) pub execution_time: Option>, /// 提案状态 pub status: ProposalStatus, } impl UpgradeProposal { pub fn new( module_name: String, current_version: Version, target_version: Version, description: String, upgrade_data: UpgradeData, proposer: String, voting_period_days: i64, ) -> Self { let proposal_id = ProposalId::generate(&module_name, &target_version); let created_at = chrono::Utc::now(); let voting_deadline = created_at + chrono::Duration::days(voting_period_days); Self { proposal_id, module_name, current_version, target_version, description, upgrade_data, proposer, created_at, voting_deadline, execution_time: None, status: ProposalStatus::Pending, } } /// 检查投票期是否结束 pub fn is_voting_period_ended(&self) -> bool { chrono::Utc::now() > self.voting_deadline } /// 开始投票 pub fn start_voting(&mut self) { self.status = ProposalStatus::Voting; } /// 批准提案 pub fn approve(&mut self) { self.status = ProposalStatus::Approved; } /// 拒绝提案 pub fn reject(&mut self) { self.status = ProposalStatus::Rejected; } /// 标记为已执行 pub fn mark_executed(&mut self) { self.status = ProposalStatus::Executed; self.execution_time = Some(chrono::Utc::now()); } /// 标记为执行失败 pub fn mark_failed(&mut self) { self.status = ProposalStatus::Failed; } /// 取消提案 pub fn cancel(&mut self) { self.status = ProposalStatus::Cancelled; } /// 检查是否可以投票 pub fn can_vote(&self) -> bool { self.status == ProposalStatus::Voting && !self.is_voting_period_ended() } /// 检查是否可以执行 pub fn can_execute(&self) -> bool { self.status == ProposalStatus::Approved } } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; #[test] fn test_proposal_id_generation() { let id = ProposalId::generate("nac-nvm", &Version::new(1, 1, 0)); assert_eq!(id.0, "nac-nvm_1.1.0"); } #[test] fn test_proposal_id_display() { let id = ProposalId::new("test-id".to_string()); assert_eq!(id.to_string(), "test-id"); } #[test] fn test_proposal_status_display() { assert_eq!(ProposalStatus::Pending.to_string(), "Pending"); assert_eq!(ProposalStatus::Voting.to_string(), "Voting"); assert_eq!(ProposalStatus::Approved.to_string(), "Approved"); } #[test] fn test_proposal_creation() { let proposal = UpgradeProposal::new( "nac-nvm".to_string(), Version::new(1, 0, 0), Version::new(1, 1, 0), "Upgrade to 1.1.0".to_string(), UpgradeData { migration_script: None, config_changes: HashMap::new(), state_migrations: vec![], breaking_changes: vec![], }, "admin".to_string(), 7, ); assert_eq!(proposal.module_name, "nac-nvm"); assert_eq!(proposal.status, ProposalStatus::Pending); assert!(!proposal.is_voting_period_ended()); } #[test] fn test_proposal_lifecycle() { let mut proposal = UpgradeProposal::new( "nac-nvm".to_string(), Version::new(1, 0, 0), Version::new(1, 1, 0), "Upgrade to 1.1.0".to_string(), UpgradeData { migration_script: None, config_changes: HashMap::new(), state_migrations: vec![], breaking_changes: vec![], }, "admin".to_string(), 7, ); // 开始投票 proposal.start_voting(); assert_eq!(proposal.status, ProposalStatus::Voting); assert!(proposal.can_vote()); // 批准 proposal.approve(); assert_eq!(proposal.status, ProposalStatus::Approved); assert!(proposal.can_execute()); assert!(!proposal.can_vote()); // 执行 proposal.mark_executed(); assert_eq!(proposal.status, ProposalStatus::Executed); assert!(proposal.execution_time.is_some()); assert!(!proposal.can_execute()); } #[test] fn test_proposal_rejection() { let mut proposal = UpgradeProposal::new( "nac-nvm".to_string(), Version::new(1, 0, 0), Version::new(1, 1, 0), "Upgrade to 1.1.0".to_string(), UpgradeData { migration_script: None, config_changes: HashMap::new(), state_migrations: vec![], breaking_changes: vec![], }, "admin".to_string(), 7, ); proposal.start_voting(); proposal.reject(); assert_eq!(proposal.status, ProposalStatus::Rejected); assert!(!proposal.can_execute()); assert!(!proposal.can_vote()); } #[test] fn test_proposal_cancellation() { let mut proposal = UpgradeProposal::new( "nac-nvm".to_string(), Version::new(1, 0, 0), Version::new(1, 1, 0), "Upgrade to 1.1.0".to_string(), UpgradeData { migration_script: None, config_changes: HashMap::new(), state_migrations: vec![], breaking_changes: vec![], }, "admin".to_string(), 7, ); proposal.cancel(); assert_eq!(proposal.status, ProposalStatus::Cancelled); assert!(!proposal.can_vote()); assert!(!proposal.can_execute()); } }