809 lines
24 KiB
Rust
809 lines
24 KiB
Rust
//! 投票权系统
|
||
//!
|
||
//! 实现证券型资产的投票机制、权重计算、投票记录和结果统计
|
||
|
||
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<String>,
|
||
}
|
||
|
||
/// 投票结果
|
||
#[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<String>,
|
||
}
|
||
|
||
/// 代理投票授权
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ProxyAuthorization {
|
||
/// 授权人
|
||
pub principal: String,
|
||
/// 代理人
|
||
pub proxy: String,
|
||
/// 授权的提案ID(如果为None则表示全部提案)
|
||
pub proposal_id: Option<String>,
|
||
/// 授权开始时间
|
||
pub valid_from: u64,
|
||
/// 授权结束时间
|
||
pub valid_until: u64,
|
||
/// 是否已撤销
|
||
pub revoked: bool,
|
||
}
|
||
|
||
/// 投票系统
|
||
#[derive(Debug)]
|
||
pub struct VotingSystem {
|
||
/// 提案列表
|
||
proposals: HashMap<String, Proposal>,
|
||
/// 投票记录
|
||
votes: HashMap<String, Vec<VoteRecord>>,
|
||
/// 投票权配置
|
||
voting_rights: HashMap<String, VotingRights>,
|
||
/// 代理授权
|
||
proxy_authorizations: Vec<ProxyAuthorization>,
|
||
/// 下一个提案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<String, String> {
|
||
// 验证参数
|
||
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<String>,
|
||
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<VotingResult, String> {
|
||
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<VotingResult, String> {
|
||
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<VoteRecord>> {
|
||
self.votes.get(proposal_id)
|
||
}
|
||
|
||
/// 获取账户的投票历史
|
||
pub fn get_voter_history(&self, voter: &str) -> Vec<VoteRecord> {
|
||
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());
|
||
}
|
||
}
|