847 lines
26 KiB
Rust
847 lines
26 KiB
Rust
//! 合规验证系统
|
||
//!
|
||
//! 实现投资者资格验证、持有限额检查、地域限制和监管报告生成
|
||
|
||
use std::collections::HashMap;
|
||
use serde::{Serialize, Deserialize};
|
||
|
||
/// 投资者类型
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||
pub enum InvestorType {
|
||
/// 零售投资者
|
||
Retail,
|
||
/// 认可投资者
|
||
Accredited,
|
||
/// 合格投资者
|
||
Qualified,
|
||
/// 机构投资者
|
||
Institutional,
|
||
}
|
||
|
||
/// 投资者资格
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct InvestorQualification {
|
||
/// 账户地址
|
||
pub account: String,
|
||
/// 投资者类型
|
||
pub investor_type: InvestorType,
|
||
/// 年收入(用于资格验证)
|
||
pub annual_income: Option<u64>,
|
||
/// 净资产
|
||
pub net_worth: Option<u64>,
|
||
/// 是否为专业投资者
|
||
pub is_professional: bool,
|
||
/// 资格认证时间
|
||
pub certified_at: u64,
|
||
/// 资格过期时间
|
||
pub expires_at: Option<u64>,
|
||
/// 认证机构
|
||
pub certifier: String,
|
||
}
|
||
|
||
/// 持有限额配置
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct HoldingLimit {
|
||
/// 限额ID
|
||
pub id: String,
|
||
/// 限额名称
|
||
pub name: String,
|
||
/// 限额类型
|
||
pub limit_type: LimitType,
|
||
/// 限额值
|
||
pub limit_value: u64,
|
||
/// 适用的投资者类型
|
||
pub applies_to: Vec<InvestorType>,
|
||
/// 是否启用
|
||
pub enabled: bool,
|
||
}
|
||
|
||
/// 限额类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum LimitType {
|
||
/// 单个账户持有上限
|
||
MaxHoldingPerAccount,
|
||
/// 单个账户持有下限
|
||
MinHoldingPerAccount,
|
||
/// 单次购买上限
|
||
MaxPurchaseAmount,
|
||
/// 总持有人数上限
|
||
MaxHolderCount,
|
||
/// 单个持有人占比上限(百分比)
|
||
MaxOwnershipPercentage,
|
||
}
|
||
|
||
/// 地域限制
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct GeographicRestriction {
|
||
/// 限制ID
|
||
pub id: String,
|
||
/// 限制类型
|
||
pub restriction_type: GeoRestrictionType,
|
||
/// 国家/地区代码列表
|
||
pub regions: Vec<String>,
|
||
/// 是否启用
|
||
pub enabled: bool,
|
||
}
|
||
|
||
/// 地域限制类型
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum GeoRestrictionType {
|
||
/// 白名单(仅允许列表中的地区)
|
||
Whitelist,
|
||
/// 黑名单(禁止列表中的地区)
|
||
Blacklist,
|
||
}
|
||
|
||
/// 投资者地域信息
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct InvestorLocation {
|
||
/// 账户地址
|
||
pub account: String,
|
||
/// 国家代码
|
||
pub country_code: String,
|
||
/// 州/省代码
|
||
pub state_code: Option<String>,
|
||
/// 验证时间
|
||
pub verified_at: u64,
|
||
}
|
||
|
||
/// 监管报告类型
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum ReportType {
|
||
/// 持有人报告
|
||
HolderReport,
|
||
/// 交易报告
|
||
TransactionReport,
|
||
/// 合规状态报告
|
||
ComplianceStatusReport,
|
||
/// 投资者分类报告
|
||
InvestorClassificationReport,
|
||
/// 地域分布报告
|
||
GeographicDistributionReport,
|
||
}
|
||
|
||
/// 监管报告
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RegulatoryReport {
|
||
/// 报告ID
|
||
pub id: String,
|
||
/// 报告类型
|
||
pub report_type: ReportType,
|
||
/// 生成时间
|
||
pub generated_at: u64,
|
||
/// 报告期间开始
|
||
pub period_start: u64,
|
||
/// 报告期间结束
|
||
pub period_end: u64,
|
||
/// 报告数据(JSON格式)
|
||
pub data: String,
|
||
/// 生成者
|
||
pub generated_by: String,
|
||
}
|
||
|
||
/// 合规检查结果
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ComplianceCheckResult {
|
||
/// 是否合规
|
||
pub compliant: bool,
|
||
/// 违规项
|
||
pub violations: Vec<String>,
|
||
/// 警告项
|
||
pub warnings: Vec<String>,
|
||
}
|
||
|
||
/// 持有人信息
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct HolderInfo {
|
||
/// 账户地址
|
||
pub account: String,
|
||
/// 持有数量
|
||
pub amount: u64,
|
||
/// 持有占比(百分比)
|
||
pub percentage: u8,
|
||
}
|
||
|
||
/// 合规验证系统
|
||
#[derive(Debug)]
|
||
pub struct ComplianceSystem {
|
||
/// 投资者资格
|
||
qualifications: HashMap<String, InvestorQualification>,
|
||
/// 持有限额配置
|
||
holding_limits: HashMap<String, HoldingLimit>,
|
||
/// 地域限制
|
||
geo_restrictions: HashMap<String, GeographicRestriction>,
|
||
/// 投资者地域信息
|
||
investor_locations: HashMap<String, InvestorLocation>,
|
||
/// 监管报告
|
||
reports: HashMap<String, RegulatoryReport>,
|
||
/// 持有人信息
|
||
holders: HashMap<[u8; 32], Vec<HolderInfo>>, // security_id -> holders
|
||
/// 下一个限额ID
|
||
next_limit_id: u64,
|
||
/// 下一个限制ID
|
||
next_restriction_id: u64,
|
||
/// 下一个报告ID
|
||
next_report_id: u64,
|
||
}
|
||
|
||
impl ComplianceSystem {
|
||
/// 创建新的合规验证系统
|
||
pub fn new() -> Self {
|
||
Self {
|
||
qualifications: HashMap::new(),
|
||
holding_limits: HashMap::new(),
|
||
geo_restrictions: HashMap::new(),
|
||
investor_locations: HashMap::new(),
|
||
reports: HashMap::new(),
|
||
holders: HashMap::new(),
|
||
next_limit_id: 1,
|
||
next_restriction_id: 1,
|
||
next_report_id: 1,
|
||
}
|
||
}
|
||
|
||
/// 设置投资者资格
|
||
pub fn set_investor_qualification(
|
||
&mut self,
|
||
account: String,
|
||
investor_type: InvestorType,
|
||
annual_income: Option<u64>,
|
||
net_worth: Option<u64>,
|
||
is_professional: bool,
|
||
certifier: String,
|
||
expires_at: Option<u64>,
|
||
) -> Result<(), String> {
|
||
// 验证资格要求
|
||
match investor_type {
|
||
InvestorType::Accredited => {
|
||
// 认可投资者需要满足收入或净资产要求
|
||
let income_qualified = annual_income.map_or(false, |i| i >= 200_000);
|
||
let net_worth_qualified = net_worth.map_or(false, |n| n >= 1_000_000);
|
||
|
||
if !income_qualified && !net_worth_qualified {
|
||
return Err("Accredited investor requirements not met".to_string());
|
||
}
|
||
}
|
||
InvestorType::Qualified => {
|
||
// 合格投资者需要更高的要求
|
||
let income_qualified = annual_income.map_or(false, |i| i >= 300_000);
|
||
let net_worth_qualified = net_worth.map_or(false, |n| n >= 2_000_000);
|
||
|
||
if !income_qualified && !net_worth_qualified {
|
||
return Err("Qualified investor requirements not met".to_string());
|
||
}
|
||
}
|
||
InvestorType::Institutional => {
|
||
// 机构投资者需要专业认证
|
||
if !is_professional {
|
||
return Err("Institutional investor must be professional".to_string());
|
||
}
|
||
}
|
||
InvestorType::Retail => {
|
||
// 零售投资者无特殊要求
|
||
}
|
||
}
|
||
|
||
let qualification = InvestorQualification {
|
||
account: account.clone(),
|
||
investor_type,
|
||
annual_income,
|
||
net_worth,
|
||
is_professional,
|
||
certified_at: Self::current_timestamp(),
|
||
expires_at,
|
||
certifier,
|
||
};
|
||
|
||
self.qualifications.insert(account, qualification);
|
||
Ok(())
|
||
}
|
||
|
||
/// 检查投资者资格
|
||
pub fn check_investor_qualification(
|
||
&self,
|
||
account: &str,
|
||
required_type: InvestorType,
|
||
) -> Result<(), String> {
|
||
let qual = self.qualifications.get(account)
|
||
.ok_or_else(|| "Investor qualification not found".to_string())?;
|
||
|
||
// 检查是否过期
|
||
if let Some(expires_at) = qual.expires_at {
|
||
if Self::current_timestamp() > expires_at {
|
||
return Err("Investor qualification has expired".to_string());
|
||
}
|
||
}
|
||
|
||
// 检查投资者类型等级
|
||
let type_level = |t: InvestorType| match t {
|
||
InvestorType::Retail => 0,
|
||
InvestorType::Accredited => 1,
|
||
InvestorType::Qualified => 2,
|
||
InvestorType::Institutional => 3,
|
||
};
|
||
|
||
if type_level(qual.investor_type) < type_level(required_type) {
|
||
return Err(format!(
|
||
"Investor type {:?} is below required type {:?}",
|
||
qual.investor_type, required_type
|
||
));
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 添加持有限额
|
||
pub fn add_holding_limit(
|
||
&mut self,
|
||
name: String,
|
||
limit_type: LimitType,
|
||
limit_value: u64,
|
||
applies_to: Vec<InvestorType>,
|
||
) -> String {
|
||
let id = format!("LIMIT-{:08}", self.next_limit_id);
|
||
self.next_limit_id += 1;
|
||
|
||
let limit = HoldingLimit {
|
||
id: id.clone(),
|
||
name,
|
||
limit_type,
|
||
limit_value,
|
||
applies_to,
|
||
enabled: true,
|
||
};
|
||
|
||
self.holding_limits.insert(id.clone(), limit);
|
||
id
|
||
}
|
||
|
||
/// 启用/禁用持有限额
|
||
pub fn set_limit_enabled(&mut self, limit_id: &str, enabled: bool) -> Result<(), String> {
|
||
let limit = self.holding_limits.get_mut(limit_id)
|
||
.ok_or_else(|| "Holding limit not found".to_string())?;
|
||
|
||
limit.enabled = enabled;
|
||
Ok(())
|
||
}
|
||
|
||
/// 更新持有人信息
|
||
pub fn update_holders(&mut self, security_id: [u8; 32], holders: Vec<HolderInfo>) {
|
||
self.holders.insert(security_id, holders);
|
||
}
|
||
|
||
/// 检查持有限额
|
||
pub fn check_holding_limits(
|
||
&self,
|
||
account: &str,
|
||
security_id: &[u8; 32],
|
||
new_amount: u64,
|
||
total_supply: u64,
|
||
) -> Result<(), String> {
|
||
let investor_type = self.qualifications.get(account)
|
||
.map(|q| q.investor_type)
|
||
.unwrap_or(InvestorType::Retail);
|
||
|
||
for limit in self.holding_limits.values() {
|
||
if !limit.enabled {
|
||
continue;
|
||
}
|
||
|
||
// 检查是否适用于该投资者类型
|
||
if !limit.applies_to.is_empty() && !limit.applies_to.contains(&investor_type) {
|
||
continue;
|
||
}
|
||
|
||
match limit.limit_type {
|
||
LimitType::MaxHoldingPerAccount => {
|
||
if new_amount > limit.limit_value {
|
||
return Err(format!(
|
||
"Holding amount {} exceeds maximum {}",
|
||
new_amount, limit.limit_value
|
||
));
|
||
}
|
||
}
|
||
LimitType::MinHoldingPerAccount => {
|
||
if new_amount > 0 && new_amount < limit.limit_value {
|
||
return Err(format!(
|
||
"Holding amount {} is below minimum {}",
|
||
new_amount, limit.limit_value
|
||
));
|
||
}
|
||
}
|
||
LimitType::MaxPurchaseAmount => {
|
||
// 这个检查应该在购买时进行
|
||
// 这里简化处理
|
||
}
|
||
LimitType::MaxHolderCount => {
|
||
if let Some(holders) = self.holders.get(security_id) {
|
||
let current_count = holders.len();
|
||
let is_new_holder = !holders.iter().any(|h| h.account == account);
|
||
|
||
if is_new_holder && current_count >= limit.limit_value as usize {
|
||
return Err(format!(
|
||
"Maximum holder count {} reached",
|
||
limit.limit_value
|
||
));
|
||
}
|
||
}
|
||
}
|
||
LimitType::MaxOwnershipPercentage => {
|
||
if total_supply > 0 {
|
||
let percentage = (new_amount * 100) / total_supply;
|
||
if percentage > limit.limit_value {
|
||
return Err(format!(
|
||
"Ownership percentage {}% exceeds maximum {}%",
|
||
percentage, limit.limit_value
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 添加地域限制
|
||
pub fn add_geographic_restriction(
|
||
&mut self,
|
||
restriction_type: GeoRestrictionType,
|
||
regions: Vec<String>,
|
||
) -> String {
|
||
let id = format!("GEO-{:08}", self.next_restriction_id);
|
||
self.next_restriction_id += 1;
|
||
|
||
let restriction = GeographicRestriction {
|
||
id: id.clone(),
|
||
restriction_type,
|
||
regions,
|
||
enabled: true,
|
||
};
|
||
|
||
self.geo_restrictions.insert(id.clone(), restriction);
|
||
id
|
||
}
|
||
|
||
/// 设置投资者地域信息
|
||
pub fn set_investor_location(
|
||
&mut self,
|
||
account: String,
|
||
country_code: String,
|
||
state_code: Option<String>,
|
||
) {
|
||
let location = InvestorLocation {
|
||
account: account.clone(),
|
||
country_code,
|
||
state_code,
|
||
verified_at: Self::current_timestamp(),
|
||
};
|
||
|
||
self.investor_locations.insert(account, location);
|
||
}
|
||
|
||
/// 检查地域限制
|
||
pub fn check_geographic_restrictions(&self, account: &str) -> Result<(), String> {
|
||
let location = self.investor_locations.get(account)
|
||
.ok_or_else(|| "Investor location not found".to_string())?;
|
||
|
||
for restriction in self.geo_restrictions.values() {
|
||
if !restriction.enabled {
|
||
continue;
|
||
}
|
||
|
||
let is_in_list = restriction.regions.contains(&location.country_code);
|
||
|
||
match restriction.restriction_type {
|
||
GeoRestrictionType::Whitelist => {
|
||
if !is_in_list {
|
||
return Err(format!(
|
||
"Country {} is not in whitelist",
|
||
location.country_code
|
||
));
|
||
}
|
||
}
|
||
GeoRestrictionType::Blacklist => {
|
||
if is_in_list {
|
||
return Err(format!(
|
||
"Country {} is blacklisted",
|
||
location.country_code
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 执行完整的合规检查
|
||
pub fn perform_compliance_check(
|
||
&self,
|
||
account: &str,
|
||
security_id: &[u8; 32],
|
||
amount: u64,
|
||
total_supply: u64,
|
||
required_investor_type: Option<InvestorType>,
|
||
) -> ComplianceCheckResult {
|
||
let mut violations = Vec::new();
|
||
let mut warnings = Vec::new();
|
||
|
||
// 检查投资者资格
|
||
if let Some(required_type) = required_investor_type {
|
||
if let Err(e) = self.check_investor_qualification(account, required_type) {
|
||
violations.push(format!("Investor qualification: {}", e));
|
||
}
|
||
}
|
||
|
||
// 检查持有限额
|
||
if let Err(e) = self.check_holding_limits(account, security_id, amount, total_supply) {
|
||
violations.push(format!("Holding limit: {}", e));
|
||
}
|
||
|
||
// 检查地域限制
|
||
if let Err(e) = self.check_geographic_restrictions(account) {
|
||
violations.push(format!("Geographic restriction: {}", e));
|
||
}
|
||
|
||
// 检查资格是否即将过期
|
||
if let Some(qual) = self.qualifications.get(account) {
|
||
if let Some(expires_at) = qual.expires_at {
|
||
let current_time = Self::current_timestamp();
|
||
let days_until_expiry = (expires_at - current_time) / 86400;
|
||
|
||
if days_until_expiry < 30 {
|
||
warnings.push(format!(
|
||
"Investor qualification expires in {} days",
|
||
days_until_expiry
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
ComplianceCheckResult {
|
||
compliant: violations.is_empty(),
|
||
violations,
|
||
warnings,
|
||
}
|
||
}
|
||
|
||
/// 生成持有人报告
|
||
pub fn generate_holder_report(
|
||
&mut self,
|
||
security_id: &[u8; 32],
|
||
generated_by: String,
|
||
) -> Result<String, String> {
|
||
let holders = self.holders.get(security_id)
|
||
.ok_or_else(|| "No holder information found".to_string())?;
|
||
|
||
let report_data = serde_json::json!({
|
||
"security_id": format!("{:?}", security_id),
|
||
"total_holders": holders.len(),
|
||
"holders": holders.iter().map(|h| {
|
||
serde_json::json!({
|
||
"account": h.account,
|
||
"amount": h.amount,
|
||
"percentage": h.percentage,
|
||
})
|
||
}).collect::<Vec<_>>(),
|
||
});
|
||
|
||
let report_id = format!("REPORT-{:08}", self.next_report_id);
|
||
self.next_report_id += 1;
|
||
|
||
let current_time = Self::current_timestamp();
|
||
let report = RegulatoryReport {
|
||
id: report_id.clone(),
|
||
report_type: ReportType::HolderReport,
|
||
generated_at: current_time,
|
||
period_start: current_time,
|
||
period_end: current_time,
|
||
data: report_data.to_string(),
|
||
generated_by,
|
||
};
|
||
|
||
self.reports.insert(report_id.clone(), report);
|
||
Ok(report_id)
|
||
}
|
||
|
||
/// 生成投资者分类报告
|
||
pub fn generate_investor_classification_report(
|
||
&mut self,
|
||
generated_by: String,
|
||
) -> String {
|
||
let mut classification_counts: HashMap<InvestorType, usize> = HashMap::new();
|
||
|
||
for qual in self.qualifications.values() {
|
||
*classification_counts.entry(qual.investor_type).or_insert(0) += 1;
|
||
}
|
||
|
||
let report_data = serde_json::json!({
|
||
"total_investors": self.qualifications.len(),
|
||
"classification": classification_counts.iter().map(|(t, c)| {
|
||
serde_json::json!({
|
||
"type": format!("{:?}", t),
|
||
"count": c,
|
||
})
|
||
}).collect::<Vec<_>>(),
|
||
});
|
||
|
||
let report_id = format!("REPORT-{:08}", self.next_report_id);
|
||
self.next_report_id += 1;
|
||
|
||
let current_time = Self::current_timestamp();
|
||
let report = RegulatoryReport {
|
||
id: report_id.clone(),
|
||
report_type: ReportType::InvestorClassificationReport,
|
||
generated_at: current_time,
|
||
period_start: current_time,
|
||
period_end: current_time,
|
||
data: report_data.to_string(),
|
||
generated_by,
|
||
};
|
||
|
||
self.reports.insert(report_id.clone(), report);
|
||
report_id
|
||
}
|
||
|
||
/// 生成地域分布报告
|
||
pub fn generate_geographic_distribution_report(
|
||
&mut self,
|
||
generated_by: String,
|
||
) -> String {
|
||
let mut country_counts: HashMap<String, usize> = HashMap::new();
|
||
|
||
for location in self.investor_locations.values() {
|
||
*country_counts.entry(location.country_code.clone()).or_insert(0) += 1;
|
||
}
|
||
|
||
let report_data = serde_json::json!({
|
||
"total_locations": self.investor_locations.len(),
|
||
"distribution": country_counts.iter().map(|(country, count)| {
|
||
serde_json::json!({
|
||
"country": country,
|
||
"count": count,
|
||
})
|
||
}).collect::<Vec<_>>(),
|
||
});
|
||
|
||
let report_id = format!("REPORT-{:08}", self.next_report_id);
|
||
self.next_report_id += 1;
|
||
|
||
let current_time = Self::current_timestamp();
|
||
let report = RegulatoryReport {
|
||
id: report_id.clone(),
|
||
report_type: ReportType::GeographicDistributionReport,
|
||
generated_at: current_time,
|
||
period_start: current_time,
|
||
period_end: current_time,
|
||
data: report_data.to_string(),
|
||
generated_by,
|
||
};
|
||
|
||
self.reports.insert(report_id.clone(), report);
|
||
report_id
|
||
}
|
||
|
||
/// 获取报告
|
||
pub fn get_report(&self, report_id: &str) -> Option<&RegulatoryReport> {
|
||
self.reports.get(report_id)
|
||
}
|
||
|
||
/// 获取所有报告
|
||
pub fn get_all_reports(&self) -> Vec<&RegulatoryReport> {
|
||
self.reports.values().collect()
|
||
}
|
||
|
||
/// 获取投资者资格
|
||
pub fn get_investor_qualification(&self, account: &str) -> Option<&InvestorQualification> {
|
||
self.qualifications.get(account)
|
||
}
|
||
|
||
/// 获取所有持有限额
|
||
pub fn get_all_holding_limits(&self) -> Vec<&HoldingLimit> {
|
||
self.holding_limits.values().collect()
|
||
}
|
||
|
||
/// 获取所有地域限制
|
||
pub fn get_all_geographic_restrictions(&self) -> Vec<&GeographicRestriction> {
|
||
self.geo_restrictions.values().collect()
|
||
}
|
||
|
||
/// 获取当前时间戳
|
||
fn current_timestamp() -> u64 {
|
||
std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.expect("mainnet: handle error")
|
||
.as_secs()
|
||
}
|
||
}
|
||
|
||
impl Default for ComplianceSystem {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_investor_qualification() {
|
||
let mut system = ComplianceSystem::new();
|
||
|
||
// 设置认可投资者
|
||
let result = system.set_investor_qualification(
|
||
"investor1".to_string(),
|
||
InvestorType::Accredited,
|
||
Some(250_000),
|
||
Some(1_500_000),
|
||
false,
|
||
"certifier1".to_string(),
|
||
None,
|
||
);
|
||
assert!(result.is_ok());
|
||
|
||
// 检查资格
|
||
assert!(system.check_investor_qualification("investor1", InvestorType::Retail).is_ok());
|
||
assert!(system.check_investor_qualification("investor1", InvestorType::Accredited).is_ok());
|
||
assert!(system.check_investor_qualification("investor1", InvestorType::Qualified).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_holding_limits() {
|
||
let mut system = ComplianceSystem::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
// 添加持有上限
|
||
system.add_holding_limit(
|
||
"Max Holding".to_string(),
|
||
LimitType::MaxHoldingPerAccount,
|
||
10000,
|
||
vec![],
|
||
);
|
||
|
||
// 检查限额
|
||
assert!(system.check_holding_limits("investor1", &security_id, 5000, 100000).is_ok());
|
||
assert!(system.check_holding_limits("investor1", &security_id, 15000, 100000).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_geographic_restrictions() {
|
||
let mut system = ComplianceSystem::new();
|
||
|
||
// 添加白名单
|
||
system.add_geographic_restriction(
|
||
GeoRestrictionType::Whitelist,
|
||
vec!["US".to_string(), "UK".to_string()],
|
||
);
|
||
|
||
// 设置投资者位置
|
||
system.set_investor_location(
|
||
"investor1".to_string(),
|
||
"US".to_string(),
|
||
Some("CA".to_string()),
|
||
);
|
||
|
||
system.set_investor_location(
|
||
"investor2".to_string(),
|
||
"CN".to_string(),
|
||
None,
|
||
);
|
||
|
||
// 检查限制
|
||
assert!(system.check_geographic_restrictions("investor1").is_ok());
|
||
assert!(system.check_geographic_restrictions("investor2").is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_compliance_check() {
|
||
let mut system = ComplianceSystem::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
// 设置投资者
|
||
system.set_investor_qualification(
|
||
"investor1".to_string(),
|
||
InvestorType::Accredited,
|
||
Some(250_000),
|
||
None,
|
||
false,
|
||
"certifier1".to_string(),
|
||
None,
|
||
).expect("mainnet: handle error");
|
||
|
||
system.set_investor_location(
|
||
"investor1".to_string(),
|
||
"US".to_string(),
|
||
None,
|
||
);
|
||
|
||
// 添加限制
|
||
system.add_holding_limit(
|
||
"Max".to_string(),
|
||
LimitType::MaxHoldingPerAccount,
|
||
10000,
|
||
vec![],
|
||
);
|
||
|
||
system.add_geographic_restriction(
|
||
GeoRestrictionType::Whitelist,
|
||
vec!["US".to_string()],
|
||
);
|
||
|
||
// 执行合规检查
|
||
let result = system.perform_compliance_check(
|
||
"investor1",
|
||
&security_id,
|
||
5000,
|
||
100000,
|
||
Some(InvestorType::Accredited),
|
||
);
|
||
|
||
assert!(result.compliant);
|
||
assert!(result.violations.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn test_generate_reports() {
|
||
let mut system = ComplianceSystem::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
// 添加持有人信息
|
||
let holders = vec![
|
||
HolderInfo {
|
||
account: "investor1".to_string(),
|
||
amount: 1000,
|
||
percentage: 50,
|
||
},
|
||
HolderInfo {
|
||
account: "investor2".to_string(),
|
||
amount: 1000,
|
||
percentage: 50,
|
||
},
|
||
];
|
||
system.update_holders(security_id, holders);
|
||
|
||
// 生成持有人报告
|
||
let report_id = system.generate_holder_report(&security_id, "admin".to_string()).expect("mainnet: handle error");
|
||
let report = system.get_report(&report_id).expect("mainnet: handle error");
|
||
assert_eq!(report.report_type, ReportType::HolderReport);
|
||
|
||
// 生成投资者分类报告
|
||
system.set_investor_qualification(
|
||
"investor1".to_string(),
|
||
InvestorType::Retail,
|
||
None,
|
||
None,
|
||
false,
|
||
"certifier1".to_string(),
|
||
None,
|
||
).expect("mainnet: handle error");
|
||
|
||
let report_id = system.generate_investor_classification_report("admin".to_string());
|
||
let report = system.get_report(&report_id).expect("mainnet: handle error");
|
||
assert_eq!(report.report_type, ReportType::InvestorClassificationReport);
|
||
}
|
||
}
|