292 lines
8.0 KiB
Rust
292 lines
8.0 KiB
Rust
//! 升级提案模块
|
|
|
|
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<chrono::Utc>,
|
|
/// 投票截止时间
|
|
pub voting_deadline: chrono::DateTime<chrono::Utc>,
|
|
/// 执行时间(可选,批准后设置)
|
|
pub execution_time: Option<chrono::DateTime<chrono::Utc>>,
|
|
/// 提案状态
|
|
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());
|
|
}
|
|
}
|