//! 投票权系统 //! //! 实现证券型资产的投票机制、权重计算、投票记录和结果统计 use std::collections::HashMap; use serde::{Serialize, Deserialize}; /// 投票提案 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Proposal { /// 提案ID pub id: String, /// 提案标题 pub title: String, /// 提案描述 pub description: String, /// 提案类型 pub proposal_type: ProposalType, /// 创建者 pub creator: String, /// 创建时间 pub created_at: u64, /// 投票开始时间 pub voting_start: u64, /// 投票结束时间 pub voting_end: u64, /// 状态 pub status: ProposalStatus, /// 需要的最小投票率(百分比) pub quorum_percentage: u8, /// 需要的赞成率(百分比) pub approval_percentage: u8, /// 证券分区ID pub security_id: [u8; 32], } /// 提案类型 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ProposalType { /// 董事会选举 BoardElection, /// 章程修改 CharterAmendment, /// 重大交易 MajorTransaction, /// 股息分配 DividendDistribution, /// 资产重组 Restructuring, /// 其他 Other, } /// 提案状态 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ProposalStatus { /// 草案 Draft, /// 投票中 Active, /// 已通过 Passed, /// 未通过 Rejected, /// 已取消 Cancelled, /// 已执行 Executed, } /// 投票选项 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum VoteOption { /// 赞成 For, /// 反对 Against, /// 弃权 Abstain, } /// 投票记录 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VoteRecord { /// 提案ID pub proposal_id: String, /// 投票人 pub voter: String, /// 投票选项 pub option: VoteOption, /// 持股数量 pub shares: u64, /// 投票权重(考虑投票倍数) pub voting_power: u64, /// 投票时间 pub timestamp: u64, /// 是否为代理投票 pub is_proxy: bool, /// 代理人(如果是代理投票) pub proxy: Option, } /// 投票结果 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VotingResult { /// 提案ID pub proposal_id: String, /// 赞成票数 pub votes_for: u64, /// 反对票数 pub votes_against: u64, /// 弃权票数 pub votes_abstain: u64, /// 总投票权重 pub total_voting_power: u64, /// 总股份数 pub total_shares: u64, /// 投票率(百分比) pub turnout_percentage: u8, /// 赞成率(百分比,基于有效票) pub approval_percentage: u8, /// 是否达到法定人数 pub quorum_reached: bool, /// 是否通过 pub passed: bool, } /// 投票权配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VotingRights { /// 账户地址 pub account: String, /// 持股数量 pub shares: u64, /// 投票倍数(例如:优先股可能有更高的投票权) pub voting_multiplier: u32, /// 是否被限制投票 pub restricted: bool, /// 限制原因 pub restriction_reason: Option, } /// 代理投票授权 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyAuthorization { /// 授权人 pub principal: String, /// 代理人 pub proxy: String, /// 授权的提案ID(如果为None则表示全部提案) pub proposal_id: Option, /// 授权开始时间 pub valid_from: u64, /// 授权结束时间 pub valid_until: u64, /// 是否已撤销 pub revoked: bool, } /// 投票系统 #[derive(Debug)] pub struct VotingSystem { /// 提案列表 proposals: HashMap, /// 投票记录 votes: HashMap>, /// 投票权配置 voting_rights: HashMap, /// 代理授权 proxy_authorizations: Vec, /// 下一个提案ID next_proposal_id: u64, } impl VotingSystem { /// 创建新的投票系统 pub fn new() -> Self { Self { proposals: HashMap::new(), votes: HashMap::new(), voting_rights: HashMap::new(), proxy_authorizations: Vec::new(), next_proposal_id: 1, } } /// 创建提案 pub fn create_proposal( &mut self, title: String, description: String, proposal_type: ProposalType, creator: String, security_id: [u8; 32], voting_start: u64, voting_end: u64, quorum_percentage: u8, approval_percentage: u8, ) -> Result { // 验证参数 if title.is_empty() { return Err("Title cannot be empty".to_string()); } if voting_start >= voting_end { return Err("Voting start must be before voting end".to_string()); } if quorum_percentage > 100 || approval_percentage > 100 { return Err("Percentages must be between 0 and 100".to_string()); } let current_time = Self::current_timestamp(); if voting_start < current_time { return Err("Voting start must be in the future".to_string()); } // 生成提案ID let proposal_id = format!("PROP-{:08}", self.next_proposal_id); self.next_proposal_id += 1; // 创建提案 let proposal = Proposal { id: proposal_id.clone(), title, description, proposal_type, creator, created_at: current_time, voting_start, voting_end, status: ProposalStatus::Draft, quorum_percentage, approval_percentage, security_id, }; self.proposals.insert(proposal_id.clone(), proposal); self.votes.insert(proposal_id.clone(), Vec::new()); Ok(proposal_id) } /// 激活提案(开始投票) pub fn activate_proposal(&mut self, proposal_id: &str) -> Result<(), String> { let proposal = self.proposals.get_mut(proposal_id) .ok_or_else(|| "Proposal not found".to_string())?; if proposal.status != ProposalStatus::Draft { return Err(format!("Proposal is not in draft status: {:?}", proposal.status)); } let current_time = Self::current_timestamp(); if current_time < proposal.voting_start { return Err("Voting period has not started yet".to_string()); } proposal.status = ProposalStatus::Active; Ok(()) } /// 设置投票权 pub fn set_voting_rights( &mut self, account: String, shares: u64, voting_multiplier: u32, ) { let rights = VotingRights { account: account.clone(), shares, voting_multiplier, restricted: false, restriction_reason: None, }; self.voting_rights.insert(account, rights); } /// 限制投票权 pub fn restrict_voting(&mut self, account: &str, reason: String) -> Result<(), String> { let rights = self.voting_rights.get_mut(account) .ok_or_else(|| "Account not found".to_string())?; rights.restricted = true; rights.restriction_reason = Some(reason); Ok(()) } /// 恢复投票权 pub fn unrestrict_voting(&mut self, account: &str) -> Result<(), String> { let rights = self.voting_rights.get_mut(account) .ok_or_else(|| "Account not found".to_string())?; rights.restricted = false; rights.restriction_reason = None; Ok(()) } /// 授权代理投票 pub fn authorize_proxy( &mut self, principal: String, proxy: String, proposal_id: Option, valid_from: u64, valid_until: u64, ) -> Result<(), String> { if principal == proxy { return Err("Cannot authorize self as proxy".to_string()); } if valid_from >= valid_until { return Err("Valid from must be before valid until".to_string()); } // 检查是否已存在相同的授权 for auth in &self.proxy_authorizations { if auth.principal == principal && auth.proxy == proxy && auth.proposal_id == proposal_id && !auth.revoked { return Err("Proxy authorization already exists".to_string()); } } let authorization = ProxyAuthorization { principal, proxy, proposal_id, valid_from, valid_until, revoked: false, }; self.proxy_authorizations.push(authorization); Ok(()) } /// 撤销代理授权 pub fn revoke_proxy( &mut self, principal: &str, proxy: &str, proposal_id: Option<&str>, ) -> Result<(), String> { let mut found = false; for auth in &mut self.proxy_authorizations { if auth.principal == principal && auth.proxy == proxy && auth.proposal_id.as_deref() == proposal_id && !auth.revoked { auth.revoked = true; found = true; } } if found { Ok(()) } else { Err("Proxy authorization not found".to_string()) } } /// 检查代理授权是否有效 fn is_proxy_valid( &self, principal: &str, proxy: &str, proposal_id: &str, ) -> bool { let current_time = Self::current_timestamp(); self.proxy_authorizations.iter().any(|auth| { auth.principal == principal && auth.proxy == proxy && !auth.revoked && auth.valid_from <= current_time && auth.valid_until >= current_time && (auth.proposal_id.is_none() || auth.proposal_id.as_deref() == Some(proposal_id)) }) } /// 投票 pub fn cast_vote( &mut self, proposal_id: &str, voter: &str, option: VoteOption, as_proxy_for: Option<&str>, ) -> Result<(), String> { // 检查提案是否存在 let proposal = self.proposals.get(proposal_id) .ok_or_else(|| "Proposal not found".to_string())?; // 检查提案状态 if proposal.status != ProposalStatus::Active { return Err(format!("Proposal is not active: {:?}", proposal.status)); } // 检查投票时间 let current_time = Self::current_timestamp(); if current_time < proposal.voting_start { return Err("Voting has not started yet".to_string()); } if current_time > proposal.voting_end { return Err("Voting has ended".to_string()); } // 确定实际投票人 let actual_voter = if let Some(principal) = as_proxy_for { // 代理投票:检查授权 if !self.is_proxy_valid(principal, voter, proposal_id) { return Err("Proxy authorization is not valid".to_string()); } principal } else { voter }; // 检查是否已投票 let votes = self.votes.get(proposal_id).unwrap(); if votes.iter().any(|v| v.voter == actual_voter) { return Err("Already voted on this proposal".to_string()); } // 获取投票权 let rights = self.voting_rights.get(actual_voter) .ok_or_else(|| "Voter has no voting rights".to_string())?; // 检查是否被限制 if rights.restricted { return Err(format!( "Voting rights restricted: {}", rights.restriction_reason.as_deref().unwrap_or("Unknown reason") )); } // 计算投票权重 let voting_power = rights.shares as u64 * rights.voting_multiplier as u64; // 创建投票记录 let vote_record = VoteRecord { proposal_id: proposal_id.to_string(), voter: actual_voter.to_string(), option, shares: rights.shares, voting_power, timestamp: current_time, is_proxy: as_proxy_for.is_some(), proxy: as_proxy_for.map(|_| voter.to_string()), }; // 添加投票记录 self.votes.get_mut(proposal_id).unwrap().push(vote_record); Ok(()) } /// 计算投票结果 pub fn calculate_result(&self, proposal_id: &str) -> Result { let proposal = self.proposals.get(proposal_id) .ok_or_else(|| "Proposal not found".to_string())?; let votes = self.votes.get(proposal_id) .ok_or_else(|| "No votes found".to_string())?; // 统计各选项的票数 let mut votes_for = 0u64; let mut votes_against = 0u64; let mut votes_abstain = 0u64; for vote in votes { match vote.option { VoteOption::For => votes_for += vote.voting_power, VoteOption::Against => votes_against += vote.voting_power, VoteOption::Abstain => votes_abstain += vote.voting_power, } } // 计算总投票权重 let total_voting_power = votes_for + votes_against + votes_abstain; // 计算总股份数(所有有投票权的股份) let total_shares: u64 = self.voting_rights.values() .filter(|r| !r.restricted) .map(|r| r.shares as u64 * r.voting_multiplier as u64) .sum(); // 计算投票率 let turnout_percentage = if total_shares > 0 { ((total_voting_power * 100) / total_shares) as u8 } else { 0 }; // 计算赞成率(基于有效票:赞成+反对) let effective_votes = votes_for + votes_against; let approval_percentage = if effective_votes > 0 { ((votes_for * 100) / effective_votes) as u8 } else { 0 }; // 检查是否达到法定人数 let quorum_reached = turnout_percentage >= proposal.quorum_percentage; // 检查是否通过 let passed = quorum_reached && approval_percentage >= proposal.approval_percentage; Ok(VotingResult { proposal_id: proposal_id.to_string(), votes_for, votes_against, votes_abstain, total_voting_power, total_shares, turnout_percentage, approval_percentage, quorum_reached, passed, }) } /// 结束投票并更新提案状态 pub fn finalize_proposal(&mut self, proposal_id: &str) -> Result { let proposal = self.proposals.get_mut(proposal_id) .ok_or_else(|| "Proposal not found".to_string())?; if proposal.status != ProposalStatus::Active { return Err(format!("Proposal is not active: {:?}", proposal.status)); } let current_time = Self::current_timestamp(); if current_time < proposal.voting_end { return Err("Voting period has not ended yet".to_string()); } // 计算结果 let result = self.calculate_result(proposal_id)?; // 更新提案状态 let proposal = self.proposals.get_mut(proposal_id).unwrap(); proposal.status = if result.passed { ProposalStatus::Passed } else { ProposalStatus::Rejected }; Ok(result) } /// 取消提案 pub fn cancel_proposal(&mut self, proposal_id: &str, canceller: &str) -> Result<(), String> { let proposal = self.proposals.get_mut(proposal_id) .ok_or_else(|| "Proposal not found".to_string())?; // 只有创建者可以取消 if proposal.creator != canceller { return Err("Only creator can cancel the proposal".to_string()); } // 只能取消草案或进行中的提案 if !matches!(proposal.status, ProposalStatus::Draft | ProposalStatus::Active) { return Err(format!("Cannot cancel proposal in status: {:?}", proposal.status)); } proposal.status = ProposalStatus::Cancelled; Ok(()) } /// 获取提案 pub fn get_proposal(&self, proposal_id: &str) -> Option<&Proposal> { self.proposals.get(proposal_id) } /// 获取所有提案 pub fn get_all_proposals(&self) -> Vec<&Proposal> { self.proposals.values().collect() } /// 获取投票记录 pub fn get_votes(&self, proposal_id: &str) -> Option<&Vec> { self.votes.get(proposal_id) } /// 获取账户的投票历史 pub fn get_voter_history(&self, voter: &str) -> Vec { self.votes.values() .flatten() .filter(|v| v.voter == voter) .cloned() .collect() } /// 获取投票权信息 pub fn get_voting_rights(&self, account: &str) -> Option<&VotingRights> { self.voting_rights.get(account) } /// 获取账户的代理授权 pub fn get_proxy_authorizations(&self, principal: &str) -> Vec<&ProxyAuthorization> { self.proxy_authorizations.iter() .filter(|auth| auth.principal == principal && !auth.revoked) .collect() } /// 获取当前时间戳 fn current_timestamp() -> u64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() } } impl Default for VotingSystem { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_proposal() { let mut system = VotingSystem::new(); let security_id = [1u8; 32]; let current_time = VotingSystem::current_timestamp(); let result = system.create_proposal( "Test Proposal".to_string(), "This is a test".to_string(), ProposalType::Other, "creator1".to_string(), security_id, current_time + 100, current_time + 1000, 50, 66, ); assert!(result.is_ok()); let proposal_id = result.unwrap(); let proposal = system.get_proposal(&proposal_id).unwrap(); assert_eq!(proposal.title, "Test Proposal"); assert_eq!(proposal.status, ProposalStatus::Draft); } #[test] fn test_voting() { let mut system = VotingSystem::new(); let security_id = [1u8; 32]; // 设置投票权 system.set_voting_rights("voter1".to_string(), 1000, 1); system.set_voting_rights("voter2".to_string(), 500, 2); // 创建提案 let current_time = VotingSystem::current_timestamp(); let proposal_id = system.create_proposal( "Test Proposal".to_string(), "Test".to_string(), ProposalType::Other, "creator1".to_string(), security_id, current_time, current_time + 1000, 50, 66, ).unwrap(); // 激活提案 system.activate_proposal(&proposal_id).unwrap(); // 投票 system.cast_vote(&proposal_id, "voter1", VoteOption::For, None).unwrap(); system.cast_vote(&proposal_id, "voter2", VoteOption::Against, None).unwrap(); // 检查投票记录 let votes = system.get_votes(&proposal_id).unwrap(); assert_eq!(votes.len(), 2); assert_eq!(votes[0].voting_power, 1000); // 1000 * 1 assert_eq!(votes[1].voting_power, 1000); // 500 * 2 } #[test] fn test_voting_result() { let mut system = VotingSystem::new(); let security_id = [1u8; 32]; system.set_voting_rights("voter1".to_string(), 1000, 1); system.set_voting_rights("voter2".to_string(), 500, 1); system.set_voting_rights("voter3".to_string(), 300, 1); let current_time = VotingSystem::current_timestamp(); let proposal_id = system.create_proposal( "Test".to_string(), "Test".to_string(), ProposalType::Other, "creator1".to_string(), security_id, current_time, current_time + 1000, 50, 66, ).unwrap(); system.activate_proposal(&proposal_id).unwrap(); // 投票:1000赞成,500反对,300弃权 system.cast_vote(&proposal_id, "voter1", VoteOption::For, None).unwrap(); system.cast_vote(&proposal_id, "voter2", VoteOption::Against, None).unwrap(); system.cast_vote(&proposal_id, "voter3", VoteOption::Abstain, None).unwrap(); let result = system.calculate_result(&proposal_id).unwrap(); assert_eq!(result.votes_for, 1000); assert_eq!(result.votes_against, 500); assert_eq!(result.votes_abstain, 300); assert_eq!(result.total_voting_power, 1800); assert_eq!(result.turnout_percentage, 100); // 1800/1800 assert_eq!(result.approval_percentage, 66); // 1000/(1000+500) assert!(result.quorum_reached); assert!(result.passed); } #[test] fn test_proxy_voting() { let mut system = VotingSystem::new(); let security_id = [1u8; 32]; system.set_voting_rights("principal1".to_string(), 1000, 1); let current_time = VotingSystem::current_timestamp(); let proposal_id = system.create_proposal( "Test".to_string(), "Test".to_string(), ProposalType::Other, "creator1".to_string(), security_id, current_time, current_time + 1000, 50, 50, ).unwrap(); // 授权代理 system.authorize_proxy( "principal1".to_string(), "proxy1".to_string(), Some(proposal_id.clone()), current_time, current_time + 2000, ).unwrap(); system.activate_proposal(&proposal_id).unwrap(); // 代理投票 system.cast_vote(&proposal_id, "proxy1", VoteOption::For, Some("principal1")).unwrap(); let votes = system.get_votes(&proposal_id).unwrap(); assert_eq!(votes.len(), 1); assert_eq!(votes[0].voter, "principal1"); assert!(votes[0].is_proxy); assert_eq!(votes[0].proxy.as_deref(), Some("proxy1")); } #[test] fn test_restrict_voting() { let mut system = VotingSystem::new(); let security_id = [1u8; 32]; system.set_voting_rights("voter1".to_string(), 1000, 1); let current_time = VotingSystem::current_timestamp(); let proposal_id = system.create_proposal( "Test".to_string(), "Test".to_string(), ProposalType::Other, "creator1".to_string(), security_id, current_time, current_time + 1000, 50, 50, ).unwrap(); system.activate_proposal(&proposal_id).unwrap(); // 限制投票权 system.restrict_voting("voter1", "Compliance issue".to_string()).unwrap(); // 尝试投票应该失败 let result = system.cast_vote(&proposal_id, "voter1", VoteOption::For, None); assert!(result.is_err()); // 恢复投票权 system.unrestrict_voting("voter1").unwrap(); // 现在投票应该成功 let result = system.cast_vote(&proposal_id, "voter1", VoteOption::For, None); assert!(result.is_ok()); } }