NAC_Blockchain/nac-upgrade-framework/src/proposal.rs

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());
}
}