feat(acc): 完整实现所有 ACC 协议族(22个协议)
- 补全 ACC-RWA、ACC-Compliance、ACC-Collateral、ACC-Redemption ACC-Insurance、ACC-Governance、ACC-XTZH、ACC-Reserve 完整实现 - 修正 mod.rs 中 ACC-1643/1644/1400/1410 的错误注释 - 所有协议严格使用 NAC 原生类型系统(Address 32字节,Hash 48字节 SHA3-384) - 完全规避以太坊模式(无 msg.sender/require/emit/ERC) - cargo check: 0 errors 协议族总计: 22个协议(4基础+8RWA+2稳定币+2工具+5证券+1兼容层)
This commit is contained in:
parent
5624717b49
commit
c1a75544be
|
|
@ -1,62 +1,179 @@
|
||||||
//! ACC协议模块
|
//! ACC-Collateral: 抵押协议
|
||||||
|
//! UID: nac.acc.ACCCollateralProtocol.v1
|
||||||
|
|
||||||
///! # ACC-Collateral: 抵押协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// CollateralPosition
|
pub enum ACCCollateralError {
|
||||||
pub struct CollateralPosition {
|
CollateralNotFound(Hash),
|
||||||
/// position_id
|
InsufficientCollateralRatio { required: u32, actual: u32 },
|
||||||
pub position_id: Hash,
|
CollateralAlreadyLiquidated(Hash),
|
||||||
/// borrower
|
CollateralFrozen(Hash),
|
||||||
pub borrower: Address,
|
InvalidConstitutionalReceipt,
|
||||||
/// lender
|
Unauthorized(Address),
|
||||||
pub lender: Address,
|
LiquidationThresholdNotReached,
|
||||||
/// collateral_asset
|
|
||||||
pub collateral_asset: Hash,
|
|
||||||
/// collateral_amount
|
|
||||||
pub collateral_amount: u128,
|
|
||||||
/// loan_amount
|
|
||||||
pub loan_amount: u128,
|
|
||||||
/// ltv_ratio
|
|
||||||
pub ltv_ratio: u8,
|
|
||||||
/// liquidation_threshold
|
|
||||||
pub liquidation_threshold: u8,
|
|
||||||
/// start_date
|
|
||||||
pub start_date: Timestamp,
|
|
||||||
/// maturity_date
|
|
||||||
pub maturity_date: Timestamp,
|
|
||||||
/// status
|
|
||||||
pub status: CollateralStatus,
|
|
||||||
}
|
}
|
||||||
|
impl std::fmt::Display for ACCCollateralError {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// CollateralStatus
|
match self {
|
||||||
pub enum CollateralStatus {
|
Self::CollateralNotFound(h) => write!(f, "抵押记录不存在: {}", h.to_hex()),
|
||||||
/// Active
|
Self::InsufficientCollateralRatio { required, actual } => write!(f, "抵押率不足: 要求 {},实际 {}", required, actual),
|
||||||
Active,
|
Self::CollateralAlreadyLiquidated(h) => write!(f, "抵押已被清算: {}", h.to_hex()),
|
||||||
/// Liquidated
|
Self::CollateralFrozen(h) => write!(f, "抵押已冻结: {}", h.to_hex()),
|
||||||
Liquidated,
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
/// Closed
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
Closed,
|
Self::LiquidationThresholdNotReached => write!(f, "未达到清算阈值"),
|
||||||
}
|
|
||||||
|
|
||||||
impl CollateralPosition {
|
|
||||||
/// new
|
|
||||||
pub fn new(borrower: Address, lender: Address, collateral_asset: Hash, collateral_amount: u128, loan_amount: u128) -> Self {
|
|
||||||
Self {
|
|
||||||
position_id: Hash::zero(),
|
|
||||||
borrower,
|
|
||||||
lender,
|
|
||||||
collateral_asset,
|
|
||||||
collateral_amount,
|
|
||||||
loan_amount,
|
|
||||||
ltv_ratio: 75,
|
|
||||||
liquidation_threshold: 85,
|
|
||||||
start_date: Timestamp::now(),
|
|
||||||
maturity_date: Timestamp::now().add_secs(365 * 24 * 3600),
|
|
||||||
status: CollateralStatus::Active,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CollateralStatus { Active, Frozen, Liquidating, Liquidated, Released }
|
||||||
|
|
||||||
|
/// 抵押记录
|
||||||
|
/// UID: nac.acc.CollateralRecord.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CollateralRecord {
|
||||||
|
pub collateral_id: Hash,
|
||||||
|
pub asset_id: Hash,
|
||||||
|
pub borrower: Address,
|
||||||
|
pub lender: Address,
|
||||||
|
/// 抵押价值(XTZH)
|
||||||
|
pub collateral_value_xtzh: u128,
|
||||||
|
/// 贷款金额(XTZH)
|
||||||
|
pub loan_amount_xtzh: u128,
|
||||||
|
/// 抵押率(基点,10000=100%)
|
||||||
|
pub collateral_ratio_bps: u32,
|
||||||
|
/// 清算阈值(基点)
|
||||||
|
pub liquidation_threshold_bps: u32,
|
||||||
|
pub status: CollateralStatus,
|
||||||
|
pub maturity_time: Timestamp,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl CollateralRecord {
|
||||||
|
pub fn current_ratio_bps(&self, current_value: u128) -> u32 {
|
||||||
|
if self.loan_amount_xtzh == 0 { return u32::MAX; }
|
||||||
|
((current_value as u64 * 10000) / self.loan_amount_xtzh as u64) as u32
|
||||||
|
}
|
||||||
|
pub fn needs_liquidation(&self, current_value: u128) -> bool {
|
||||||
|
self.current_ratio_bps(current_value) < self.liquidation_threshold_bps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum CollateralProtocolEvent {
|
||||||
|
CollateralCreated { collateral_id: Hash, asset_id: Hash, borrower: Address, loan_amount: u128, timestamp: Timestamp },
|
||||||
|
CollateralLiquidated { collateral_id: Hash, liquidator: Address, recovered_amount: u128, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
CollateralReleased { collateral_id: Hash, timestamp: Timestamp },
|
||||||
|
CollateralValueUpdated { collateral_id: Hash, old_value: u128, new_value: u128, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-Collateral 抵押协议
|
||||||
|
/// UID: nac.acc.ACCCollateralProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCCollateralProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub collaterals: HashMap<Hash, CollateralRecord>,
|
||||||
|
/// 最低抵押率(基点,默认15000=150%)
|
||||||
|
pub min_collateral_ratio_bps: u32,
|
||||||
|
/// 清算阈值(基点,默认12000=120%)
|
||||||
|
pub liquidation_threshold_bps: u32,
|
||||||
|
pub pending_events: Vec<CollateralProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCCollateralProtocol {
|
||||||
|
pub fn new(min_collateral_ratio_bps: u32, liquidation_threshold_bps: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCCollateralProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Collateral".to_string(),
|
||||||
|
collaterals: HashMap::new(),
|
||||||
|
min_collateral_ratio_bps,
|
||||||
|
liquidation_threshold_bps,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn create_collateral(
|
||||||
|
&mut self, asset_id: Hash, borrower: Address, lender: Address,
|
||||||
|
collateral_value_xtzh: u128, loan_amount_xtzh: u128, maturity_secs: u64,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCCollateralError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCCollateralError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
let ratio_bps = if loan_amount_xtzh > 0 {
|
||||||
|
((collateral_value_xtzh as u64 * 10000) / loan_amount_xtzh as u64) as u32
|
||||||
|
} else { u32::MAX };
|
||||||
|
if ratio_bps < self.min_collateral_ratio_bps {
|
||||||
|
return Err(ACCCollateralError::InsufficientCollateralRatio {
|
||||||
|
required: self.min_collateral_ratio_bps, actual: ratio_bps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(asset_id.as_bytes());
|
||||||
|
data.extend_from_slice(borrower.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let collateral_id = Hash::sha3_384(&data);
|
||||||
|
let record = CollateralRecord {
|
||||||
|
collateral_id, asset_id, borrower: borrower.clone(), lender,
|
||||||
|
collateral_value_xtzh, loan_amount_xtzh,
|
||||||
|
collateral_ratio_bps: ratio_bps,
|
||||||
|
liquidation_threshold_bps: self.liquidation_threshold_bps,
|
||||||
|
status: CollateralStatus::Active,
|
||||||
|
maturity_time: timestamp.add_secs(maturity_secs),
|
||||||
|
constitutional_receipt,
|
||||||
|
created_at: timestamp.clone(), updated_at: timestamp.clone(),
|
||||||
|
};
|
||||||
|
self.collaterals.insert(collateral_id, record);
|
||||||
|
self.pending_events.push(CollateralProtocolEvent::CollateralCreated {
|
||||||
|
collateral_id, asset_id, borrower, loan_amount: loan_amount_xtzh, timestamp,
|
||||||
|
});
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(collateral_id)
|
||||||
|
}
|
||||||
|
pub fn liquidate(
|
||||||
|
&mut self, collateral_id: Hash, liquidator: Address, current_value: u128,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<u128, ACCCollateralError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCCollateralError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
let record = self.collaterals.get_mut(&collateral_id)
|
||||||
|
.ok_or(ACCCollateralError::CollateralNotFound(collateral_id))?;
|
||||||
|
if record.status == CollateralStatus::Liquidated {
|
||||||
|
return Err(ACCCollateralError::CollateralAlreadyLiquidated(collateral_id));
|
||||||
|
}
|
||||||
|
if !record.needs_liquidation(current_value) {
|
||||||
|
return Err(ACCCollateralError::LiquidationThresholdNotReached);
|
||||||
|
}
|
||||||
|
let recovered = current_value.min(record.loan_amount_xtzh);
|
||||||
|
record.status = CollateralStatus::Liquidated;
|
||||||
|
record.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(CollateralProtocolEvent::CollateralLiquidated {
|
||||||
|
collateral_id, liquidator, recovered_amount: recovered, timestamp, constitutional_receipt,
|
||||||
|
});
|
||||||
|
Ok(recovered)
|
||||||
|
}
|
||||||
|
pub fn release_collateral(
|
||||||
|
&mut self, collateral_id: Hash, initiator: Address, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCCollateralError> {
|
||||||
|
let record = self.collaterals.get_mut(&collateral_id)
|
||||||
|
.ok_or(ACCCollateralError::CollateralNotFound(collateral_id))?;
|
||||||
|
if initiator != record.borrower && initiator != record.lender {
|
||||||
|
return Err(ACCCollateralError::Unauthorized(initiator));
|
||||||
|
}
|
||||||
|
record.status = CollateralStatus::Released;
|
||||||
|
record.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(CollateralProtocolEvent::CollateralReleased { collateral_id, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get_collateral(&self, id: &Hash) -> Option<&CollateralRecord> { self.collaterals.get(id) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<CollateralProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,177 @@
|
||||||
//! ACC协议模块
|
//! acc_compliance - NAC 原生协议实现
|
||||||
|
//! 从 acc_remaining_protocols.rs 提取
|
||||||
///! # ACC-Compliance: 合规协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use crate::l1_protocol::gnacs::{ComplianceLevel, Jurisdiction};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::{Deserialize, Serialize};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// ComplianceRecord
|
pub enum ACCComplianceError {
|
||||||
pub struct ComplianceRecord {
|
EntityNotFound(Address),
|
||||||
/// entity
|
ComplianceCheckFailed { layer: u8, reason: String },
|
||||||
pub entity: Address,
|
InvalidConstitutionalReceipt,
|
||||||
/// jurisdiction
|
Unauthorized(Address),
|
||||||
pub jurisdiction: Jurisdiction,
|
BlacklistedEntity(Address),
|
||||||
/// compliance_level
|
JurisdictionRestricted { entity: Address, jurisdiction: String },
|
||||||
pub compliance_level: ComplianceLevel,
|
}
|
||||||
/// kyc_verified
|
impl std::fmt::Display for ACCComplianceError {
|
||||||
pub kyc_verified: bool,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// aml_verified
|
match self {
|
||||||
pub aml_verified: bool,
|
Self::EntityNotFound(a) => write!(f, "实体不存在: {}", a.to_hex()),
|
||||||
/// accreditation_verified
|
Self::ComplianceCheckFailed { layer, reason } => write!(f, "第{}层合规检查失败: {}", layer, reason),
|
||||||
pub accreditation_verified: bool,
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
/// verification_date
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
pub verification_date: Timestamp,
|
Self::BlacklistedEntity(a) => write!(f, "黑名单实体: {}", a.to_hex()),
|
||||||
/// expiry_date
|
Self::JurisdictionRestricted { entity, jurisdiction } => write!(f, "司法管辖区限制 {} 在 {}", entity.to_hex(), jurisdiction),
|
||||||
pub expiry_date: Timestamp,
|
}
|
||||||
/// verifier
|
|
||||||
pub verifier: Address,
|
|
||||||
/// document_hash
|
|
||||||
pub document_hash: Hash,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComplianceRecord {
|
|
||||||
/// new
|
|
||||||
pub fn new(entity: Address, jurisdiction: Jurisdiction) -> Self {
|
|
||||||
Self {
|
|
||||||
entity,
|
|
||||||
jurisdiction,
|
|
||||||
compliance_level: ComplianceLevel::Unknown,
|
|
||||||
kyc_verified: false,
|
|
||||||
aml_verified: false,
|
|
||||||
accreditation_verified: false,
|
|
||||||
verification_date: Timestamp::now(),
|
|
||||||
expiry_date: Timestamp::now().add_secs(365 * 24 * 3600),
|
|
||||||
verifier: Address::zero(),
|
|
||||||
document_hash: Hash::zero(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// is_valid
|
|
||||||
pub fn is_valid(&self) -> bool {
|
/// 七层合规验证结果
|
||||||
!self.expiry_date.is_expired(Timestamp::now().as_secs())
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SevenLayerComplianceResult {
|
||||||
|
pub entity: Address,
|
||||||
|
/// L1: 身份验证
|
||||||
|
pub l1_identity: bool,
|
||||||
|
/// L2: KYC/AML
|
||||||
|
pub l2_kyc_aml: bool,
|
||||||
|
/// L3: 司法管辖区合规
|
||||||
|
pub l3_jurisdiction: bool,
|
||||||
|
/// L4: 资产合规
|
||||||
|
pub l4_asset: bool,
|
||||||
|
/// L5: 交易合规
|
||||||
|
pub l5_transaction: bool,
|
||||||
|
/// L6: AI 合规评分
|
||||||
|
pub l6_ai_score: u8,
|
||||||
|
/// L7: 宪法合规(CBPP)
|
||||||
|
pub l7_constitutional: bool,
|
||||||
|
pub overall_pass: bool,
|
||||||
|
pub checked_at: Timestamp,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
}
|
||||||
|
impl SevenLayerComplianceResult {
|
||||||
|
pub fn is_fully_compliant(&self) -> bool {
|
||||||
|
self.l1_identity && self.l2_kyc_aml && self.l3_jurisdiction
|
||||||
|
&& self.l4_asset && self.l5_transaction
|
||||||
|
&& self.l6_ai_score >= 70 && self.l7_constitutional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ComplianceRecord {
|
||||||
|
pub entity: Address,
|
||||||
|
pub kyc_verified: bool,
|
||||||
|
pub aml_cleared: bool,
|
||||||
|
pub allowed_jurisdictions: Vec<String>,
|
||||||
|
pub blacklisted: bool,
|
||||||
|
pub ai_risk_score: u8,
|
||||||
|
pub last_checked: Timestamp,
|
||||||
|
pub compliance_hash: Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ComplianceProtocolEvent {
|
||||||
|
EntityRegistered { entity: Address, timestamp: Timestamp },
|
||||||
|
ComplianceChecked { entity: Address, result: bool, timestamp: Timestamp },
|
||||||
|
EntityBlacklisted { entity: Address, reason: String, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
EntityWhitelisted { entity: Address, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-Compliance 七层合规验证协议
|
||||||
|
/// UID: nac.acc.ACCComplianceProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCComplianceProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub compliance_records: HashMap<Address, ComplianceRecord>,
|
||||||
|
pub blacklist: HashMap<Address, String>,
|
||||||
|
pub compliance_history: HashMap<Address, Vec<SevenLayerComplianceResult>>,
|
||||||
|
pub pending_events: Vec<ComplianceProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCComplianceProtocol {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCComplianceProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Compliance".to_string(),
|
||||||
|
compliance_records: HashMap::new(),
|
||||||
|
blacklist: HashMap::new(),
|
||||||
|
compliance_history: HashMap::new(),
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn register_entity(
|
||||||
|
&mut self, entity: Address, kyc_verified: bool, aml_cleared: bool,
|
||||||
|
allowed_jurisdictions: Vec<String>, ai_risk_score: u8,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCComplianceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCComplianceError::InvalidConstitutionalReceipt); }
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(entity.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let compliance_hash = Hash::sha3_384(&data);
|
||||||
|
let record = ComplianceRecord {
|
||||||
|
entity: entity.clone(), kyc_verified, aml_cleared,
|
||||||
|
allowed_jurisdictions, blacklisted: false, ai_risk_score,
|
||||||
|
last_checked: timestamp.clone(), compliance_hash,
|
||||||
|
};
|
||||||
|
self.compliance_records.insert(entity.clone(), record);
|
||||||
|
self.pending_events.push(ComplianceProtocolEvent::EntityRegistered { entity, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn run_seven_layer_check(
|
||||||
|
&mut self, entity: Address, jurisdiction: &str,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<SevenLayerComplianceResult, ACCComplianceError> {
|
||||||
|
if self.blacklist.contains_key(&entity) {
|
||||||
|
return Err(ACCComplianceError::BlacklistedEntity(entity));
|
||||||
|
}
|
||||||
|
let record = self.compliance_records.get(&entity)
|
||||||
|
.ok_or(ACCComplianceError::EntityNotFound(entity.clone()))?;
|
||||||
|
let l1 = true; // 地址存在即通过身份验证
|
||||||
|
let l2 = record.kyc_verified && record.aml_cleared;
|
||||||
|
let l3 = record.allowed_jurisdictions.is_empty()
|
||||||
|
|| record.allowed_jurisdictions.iter().any(|j| j == jurisdiction);
|
||||||
|
let l4 = !record.blacklisted;
|
||||||
|
let l5 = record.ai_risk_score < 80; // 风险评分低于80才允许
|
||||||
|
let l6 = 100u8.saturating_sub(record.ai_risk_score);
|
||||||
|
let l7 = !constitutional_receipt.is_zero();
|
||||||
|
let overall = l1 && l2 && l3 && l4 && l5 && l6 >= 70 && l7;
|
||||||
|
let result = SevenLayerComplianceResult {
|
||||||
|
entity: entity.clone(), l1_identity: l1, l2_kyc_aml: l2,
|
||||||
|
l3_jurisdiction: l3, l4_asset: l4, l5_transaction: l5,
|
||||||
|
l6_ai_score: l6, l7_constitutional: l7, overall_pass: overall,
|
||||||
|
checked_at: timestamp.clone(), constitutional_receipt,
|
||||||
|
};
|
||||||
|
self.compliance_history.entry(entity.clone()).or_insert_with(Vec::new).push(result.clone());
|
||||||
|
self.pending_events.push(ComplianceProtocolEvent::ComplianceChecked {
|
||||||
|
entity, result: overall, timestamp,
|
||||||
|
});
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
pub fn blacklist_entity(
|
||||||
|
&mut self, entity: Address, reason: String,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCComplianceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCComplianceError::InvalidConstitutionalReceipt); }
|
||||||
|
self.blacklist.insert(entity.clone(), reason.clone());
|
||||||
|
if let Some(record) = self.compliance_records.get_mut(&entity) {
|
||||||
|
record.blacklisted = true;
|
||||||
|
}
|
||||||
|
self.pending_events.push(ComplianceProtocolEvent::EntityBlacklisted {
|
||||||
|
entity, reason, timestamp, constitutional_receipt,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn is_compliant(&self, entity: &Address) -> bool {
|
||||||
|
if self.blacklist.contains_key(entity) { return false; }
|
||||||
|
self.compliance_records.get(entity)
|
||||||
|
.map(|r| r.kyc_verified && r.aml_cleared && !r.blacklisted)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
pub fn get_record(&self, entity: &Address) -> Option<&ComplianceRecord> { self.compliance_records.get(entity) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<ComplianceProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,245 @@
|
||||||
//! ACC协议模块
|
//! ACC-Custody: 资产托管协议
|
||||||
|
//! UID: nac.acc.ACCCustodyProtocol.v1
|
||||||
|
//! 完全规避以太坊模式,使用 NAC 原生类型系统
|
||||||
|
|
||||||
///! # ACC-Custody: 托管协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// CustodyAgreement
|
pub enum ACCCustodyError {
|
||||||
pub struct CustodyAgreement {
|
CustodyNotFound(Hash),
|
||||||
/// agreement_id
|
CustodyAlreadyExists(Hash),
|
||||||
pub agreement_id: Hash,
|
UnauthorizedCustodian(Address),
|
||||||
/// asset_id
|
CustodyAlreadyTerminated(Hash),
|
||||||
pub asset_id: Hash,
|
InvalidConstitutionalReceipt,
|
||||||
/// owner
|
InsufficientInsurance,
|
||||||
pub owner: Address,
|
Unauthorized(Address),
|
||||||
/// custodian
|
|
||||||
pub custodian: Address,
|
|
||||||
/// custody_type
|
|
||||||
pub custody_type: CustodyType,
|
|
||||||
/// start_date
|
|
||||||
pub start_date: Timestamp,
|
|
||||||
/// end_date
|
|
||||||
pub end_date: Option<Timestamp>,
|
|
||||||
/// insurance_amount
|
|
||||||
pub insurance_amount: u128,
|
|
||||||
/// agreement_hash
|
|
||||||
pub agreement_hash: Hash,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
impl std::fmt::Display for ACCCustodyError {
|
||||||
/// CustodyType
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub enum CustodyType {
|
match self {
|
||||||
/// Physical
|
Self::CustodyNotFound(h) => write!(f, "托管协议不存在: {}", h.to_hex()),
|
||||||
Physical,
|
Self::CustodyAlreadyExists(h) => write!(f, "托管协议已存在: {}", h.to_hex()),
|
||||||
/// Digital
|
Self::UnauthorizedCustodian(a) => write!(f, "未授权托管机构: {}", a.to_hex()),
|
||||||
Digital,
|
Self::CustodyAlreadyTerminated(h) => write!(f, "托管协议已终止: {}", h.to_hex()),
|
||||||
/// Hybrid
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
Hybrid,
|
Self::InsufficientInsurance => write!(f, "保险金额不足"),
|
||||||
}
|
Self::Unauthorized(a) => write!(f, "未授权操作: {}", a.to_hex()),
|
||||||
|
|
||||||
impl CustodyAgreement {
|
|
||||||
/// new
|
|
||||||
pub fn new(asset_id: Hash, owner: Address, custodian: Address) -> Self {
|
|
||||||
Self {
|
|
||||||
agreement_id: Hash::zero(),
|
|
||||||
asset_id,
|
|
||||||
owner,
|
|
||||||
custodian,
|
|
||||||
custody_type: CustodyType::Physical,
|
|
||||||
start_date: Timestamp::now(),
|
|
||||||
end_date: None,
|
|
||||||
insurance_amount: 0,
|
|
||||||
agreement_hash: Hash::zero(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CustodyType {
|
||||||
|
Physical,
|
||||||
|
Digital,
|
||||||
|
Hybrid,
|
||||||
|
SelfCustody,
|
||||||
|
ThirdPartyInstitutional,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum CustodyStatus {
|
||||||
|
Pending,
|
||||||
|
Active,
|
||||||
|
Suspended,
|
||||||
|
Terminated,
|
||||||
|
InDispute,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 托管协议
|
||||||
|
/// UID: nac.acc.CustodyAgreement.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CustodyAgreement {
|
||||||
|
pub custody_id: Hash,
|
||||||
|
pub asset_id: Hash,
|
||||||
|
pub owner: Address,
|
||||||
|
pub custodian: Address,
|
||||||
|
pub custody_type: CustodyType,
|
||||||
|
pub status: CustodyStatus,
|
||||||
|
pub start_time: Timestamp,
|
||||||
|
pub end_time: Option<Timestamp>,
|
||||||
|
/// 保险金额(XTZH)
|
||||||
|
pub insurance_amount: u128,
|
||||||
|
pub insurer: Option<Address>,
|
||||||
|
/// 托管费率(基点,1基点=0.01%)
|
||||||
|
pub fee_basis_points: u16,
|
||||||
|
/// 托管协议文档哈希(SHA3-384)
|
||||||
|
pub agreement_document_hash: Hash,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum CustodyProtocolEvent {
|
||||||
|
CustodyCreated { custody_id: Hash, asset_id: Hash, custodian: Address, timestamp: Timestamp },
|
||||||
|
CustodyActivated { custody_id: Hash, timestamp: Timestamp },
|
||||||
|
CustodyTerminated { custody_id: Hash, reason: String, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
CustodySuspended { custody_id: Hash, reason: String, timestamp: Timestamp },
|
||||||
|
InsuranceUpdated { custody_id: Hash, old_amount: u128, new_amount: u128, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-Custody 托管协议
|
||||||
|
/// UID: nac.acc.ACCCustodyProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCCustodyProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub agreements: HashMap<Hash, CustodyAgreement>,
|
||||||
|
/// 资产到托管协议的映射(asset_id -> custody_id)
|
||||||
|
pub asset_custody_map: HashMap<Hash, Hash>,
|
||||||
|
pub authorized_custodians: HashMap<Address, Timestamp>,
|
||||||
|
/// 最低保险要求(XTZH)
|
||||||
|
pub min_insurance_requirement: u128,
|
||||||
|
pub pending_events: Vec<CustodyProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ACCCustodyProtocol {
|
||||||
|
pub fn new(min_insurance_requirement: u128) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCCustodyProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Custody".to_string(),
|
||||||
|
agreements: HashMap::new(),
|
||||||
|
asset_custody_map: HashMap::new(),
|
||||||
|
authorized_custodians: HashMap::new(),
|
||||||
|
min_insurance_requirement,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authorize_custodian(
|
||||||
|
&mut self,
|
||||||
|
custodian: Address,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCCustodyError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCCustodyError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
self.authorized_custodians.insert(custodian, timestamp);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_custody(
|
||||||
|
&mut self,
|
||||||
|
asset_id: Hash,
|
||||||
|
owner: Address,
|
||||||
|
custodian: Address,
|
||||||
|
custody_type: CustodyType,
|
||||||
|
insurance_amount: u128,
|
||||||
|
insurer: Option<Address>,
|
||||||
|
fee_basis_points: u16,
|
||||||
|
agreement_document_hash: Hash,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCCustodyError> {
|
||||||
|
if custody_type != CustodyType::SelfCustody
|
||||||
|
&& !self.authorized_custodians.contains_key(&custodian)
|
||||||
|
{
|
||||||
|
return Err(ACCCustodyError::UnauthorizedCustodian(custodian));
|
||||||
|
}
|
||||||
|
if insurance_amount < self.min_insurance_requirement {
|
||||||
|
return Err(ACCCustodyError::InsufficientInsurance);
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(asset_id.as_bytes());
|
||||||
|
data.extend_from_slice(custodian.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let custody_id = Hash::sha3_384(&data);
|
||||||
|
if self.agreements.contains_key(&custody_id) {
|
||||||
|
return Err(ACCCustodyError::CustodyAlreadyExists(custody_id));
|
||||||
|
}
|
||||||
|
let agreement = CustodyAgreement {
|
||||||
|
custody_id,
|
||||||
|
asset_id,
|
||||||
|
owner,
|
||||||
|
custodian: custodian.clone(),
|
||||||
|
custody_type,
|
||||||
|
status: CustodyStatus::Pending,
|
||||||
|
start_time: timestamp.clone(),
|
||||||
|
end_time: None,
|
||||||
|
insurance_amount,
|
||||||
|
insurer,
|
||||||
|
fee_basis_points,
|
||||||
|
agreement_document_hash,
|
||||||
|
constitutional_receipt,
|
||||||
|
created_at: timestamp.clone(),
|
||||||
|
updated_at: timestamp.clone(),
|
||||||
|
};
|
||||||
|
self.asset_custody_map.insert(asset_id, custody_id);
|
||||||
|
self.agreements.insert(custody_id, agreement);
|
||||||
|
self.pending_events.push(CustodyProtocolEvent::CustodyCreated {
|
||||||
|
custody_id,
|
||||||
|
asset_id,
|
||||||
|
custodian,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(custody_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_custody(
|
||||||
|
&mut self,
|
||||||
|
custody_id: Hash,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCCustodyError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCCustodyError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
let agreement = self.agreements.get_mut(&custody_id)
|
||||||
|
.ok_or(ACCCustodyError::CustodyNotFound(custody_id))?;
|
||||||
|
agreement.status = CustodyStatus::Active;
|
||||||
|
agreement.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(CustodyProtocolEvent::CustodyActivated { custody_id, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminate_custody(
|
||||||
|
&mut self,
|
||||||
|
custody_id: Hash,
|
||||||
|
reason: String,
|
||||||
|
initiator: Address,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCCustodyError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCCustodyError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
let agreement = self.agreements.get_mut(&custody_id)
|
||||||
|
.ok_or(ACCCustodyError::CustodyNotFound(custody_id))?;
|
||||||
|
if agreement.status == CustodyStatus::Terminated {
|
||||||
|
return Err(ACCCustodyError::CustodyAlreadyTerminated(custody_id));
|
||||||
|
}
|
||||||
|
if initiator != agreement.owner && initiator != agreement.custodian {
|
||||||
|
return Err(ACCCustodyError::Unauthorized(initiator));
|
||||||
|
}
|
||||||
|
agreement.status = CustodyStatus::Terminated;
|
||||||
|
agreement.end_time = Some(timestamp.clone());
|
||||||
|
agreement.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(CustodyProtocolEvent::CustodyTerminated {
|
||||||
|
custody_id, reason, timestamp, constitutional_receipt,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_custody(&self, custody_id: &Hash) -> Option<&CustodyAgreement> {
|
||||||
|
self.agreements.get(custody_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_asset_custody(&self, asset_id: &Hash) -> Option<&CustodyAgreement> {
|
||||||
|
self.asset_custody_map.get(asset_id)
|
||||||
|
.and_then(|id| self.agreements.get(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<CustodyProtocolEvent> {
|
||||||
|
std::mem::take(&mut self.pending_events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,80 +1,192 @@
|
||||||
//! ACC协议模块
|
//! acc_governance - NAC 原生协议实现
|
||||||
|
|
||||||
///! # ACC-Governance: 治理协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// GovernanceProposal
|
pub enum ACCGovernanceError {
|
||||||
pub struct ACCGovernanceProposal {
|
ProposalNotFound(Hash),
|
||||||
/// proposal_id
|
ProposalAlreadyExecuted(Hash),
|
||||||
pub proposal_id: Hash,
|
VotingPeriodEnded(Hash),
|
||||||
/// proposer
|
AlreadyVoted(Address),
|
||||||
pub proposer: Address,
|
InsufficientVotingPower { required: u128, actual: u128 },
|
||||||
/// title
|
QuorumNotReached { required: u128, actual: u128 },
|
||||||
pub title: String,
|
InvalidConstitutionalReceipt,
|
||||||
/// description
|
Unauthorized(Address),
|
||||||
pub description: String,
|
}
|
||||||
/// proposal_type
|
impl std::fmt::Display for ACCGovernanceError {
|
||||||
pub proposal_type: ACCProposalType,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// created_at
|
match self {
|
||||||
pub created_at: Timestamp,
|
Self::ProposalNotFound(h) => write!(f, "提案不存在: {}", h.to_hex()),
|
||||||
/// voting_start
|
Self::ProposalAlreadyExecuted(h) => write!(f, "提案已执行: {}", h.to_hex()),
|
||||||
pub voting_start: Timestamp,
|
Self::VotingPeriodEnded(h) => write!(f, "投票期已结束: {}", h.to_hex()),
|
||||||
/// voting_end
|
Self::AlreadyVoted(a) => write!(f, "已投票: {}", a.to_hex()),
|
||||||
pub voting_end: Timestamp,
|
Self::InsufficientVotingPower { required, actual } => write!(f, "投票权不足: 需要 {},实际 {}", required, actual),
|
||||||
/// votes_for
|
Self::QuorumNotReached { required, actual } => write!(f, "未达法定人数: 需要 {},实际 {}", required, actual),
|
||||||
pub votes_for: u128,
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
/// votes_against
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
pub votes_against: u128,
|
}
|
||||||
/// status
|
|
||||||
pub status: ACCProposalStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// ProposalType
|
|
||||||
pub enum ACCProposalType {
|
|
||||||
/// ParameterChange
|
|
||||||
ParameterChange,
|
|
||||||
/// ProtocolUpgrade
|
|
||||||
ProtocolUpgrade,
|
|
||||||
/// TreasuryAllocation
|
|
||||||
TreasuryAllocation,
|
|
||||||
/// Emergency
|
|
||||||
Emergency,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// ProposalStatus
|
|
||||||
pub enum ACCProposalStatus {
|
|
||||||
/// Pending
|
|
||||||
Pending,
|
|
||||||
/// Active
|
|
||||||
Active,
|
|
||||||
/// Passed
|
|
||||||
Passed,
|
|
||||||
/// Rejected
|
|
||||||
Rejected,
|
|
||||||
/// Executed
|
|
||||||
Executed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ACCGovernanceProposal {
|
|
||||||
/// new
|
|
||||||
pub fn new(proposer: Address, title: String, description: String) -> Self {
|
|
||||||
let now = Timestamp::now();
|
|
||||||
Self {
|
|
||||||
proposal_id: Hash::zero(),
|
|
||||||
proposer,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
proposal_type: ACCProposalType::ParameterChange,
|
|
||||||
created_at: now,
|
|
||||||
voting_start: now.add_secs(24 * 3600),
|
|
||||||
voting_end: now.add_secs(7 * 24 * 3600),
|
|
||||||
votes_for: 0,
|
|
||||||
votes_against: 0,
|
|
||||||
status: ACCProposalStatus::Pending,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ProposalType {
|
||||||
|
ParameterChange { parameter: String, new_value: Vec<u8> },
|
||||||
|
ProtocolUpgrade { new_version: String, upgrade_hash: Hash },
|
||||||
|
AssetPolicyChange { asset_id: Hash, policy_hash: Hash },
|
||||||
|
EmergencyPause { reason: String },
|
||||||
|
GovernanceMemberChange { member: Address, action: MemberAction },
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum MemberAction { Add, Remove }
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum ProposalStatus { Active, Passed, Rejected, Executed, Cancelled, Expired }
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum VoteChoice { For, Against, Abstain }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct GovernanceProposal {
|
||||||
|
pub proposal_id: Hash,
|
||||||
|
pub proposer: Address,
|
||||||
|
pub proposal_type: ProposalType,
|
||||||
|
pub description: String,
|
||||||
|
pub status: ProposalStatus,
|
||||||
|
pub votes_for: u128,
|
||||||
|
pub votes_against: u128,
|
||||||
|
pub votes_abstain: u128,
|
||||||
|
pub vote_records: HashMap<Address, VoteChoice>,
|
||||||
|
pub voting_start: Timestamp,
|
||||||
|
pub voting_end: Timestamp,
|
||||||
|
pub quorum_bps: u32,
|
||||||
|
pub pass_threshold_bps: u32,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub executed_at: Option<Timestamp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum GovernanceProtocolEvent {
|
||||||
|
ProposalCreated { proposal_id: Hash, proposer: Address, timestamp: Timestamp },
|
||||||
|
VoteCast { proposal_id: Hash, voter: Address, choice: VoteChoice, voting_power: u128, timestamp: Timestamp },
|
||||||
|
ProposalPassed { proposal_id: Hash, votes_for: u128, votes_against: u128, timestamp: Timestamp },
|
||||||
|
ProposalRejected { proposal_id: Hash, reason: String, timestamp: Timestamp },
|
||||||
|
ProposalExecuted { proposal_id: Hash, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCGovernanceProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub proposals: HashMap<Hash, GovernanceProposal>,
|
||||||
|
pub voting_power_registry: HashMap<Address, u128>,
|
||||||
|
pub total_voting_power: u128,
|
||||||
|
pub default_quorum_bps: u32,
|
||||||
|
pub default_pass_threshold_bps: u32,
|
||||||
|
pub pending_events: Vec<GovernanceProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCGovernanceProtocol {
|
||||||
|
pub fn new(default_quorum_bps: u32, default_pass_threshold_bps: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCGovernanceProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Governance".to_string(),
|
||||||
|
proposals: HashMap::new(),
|
||||||
|
voting_power_registry: HashMap::new(),
|
||||||
|
total_voting_power: 0,
|
||||||
|
default_quorum_bps, default_pass_threshold_bps,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(), updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn register_voting_power(&mut self, address: Address, power: u128) {
|
||||||
|
let old = self.voting_power_registry.insert(address, power).unwrap_or(0);
|
||||||
|
self.total_voting_power = self.total_voting_power.saturating_sub(old).saturating_add(power);
|
||||||
|
}
|
||||||
|
pub fn create_proposal(
|
||||||
|
&mut self, proposer: Address, proposal_type: ProposalType, description: String,
|
||||||
|
voting_duration_secs: u64, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCGovernanceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCGovernanceError::InvalidConstitutionalReceipt); }
|
||||||
|
let proposer_power = self.voting_power_registry.get(&proposer).copied().unwrap_or(0);
|
||||||
|
if proposer_power == 0 {
|
||||||
|
return Err(ACCGovernanceError::InsufficientVotingPower { required: 1, actual: 0 });
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(proposer.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let proposal_id = Hash::sha3_384(&data);
|
||||||
|
let proposal = GovernanceProposal {
|
||||||
|
proposal_id, proposer: proposer.clone(), proposal_type, description,
|
||||||
|
status: ProposalStatus::Active,
|
||||||
|
votes_for: 0, votes_against: 0, votes_abstain: 0,
|
||||||
|
vote_records: HashMap::new(),
|
||||||
|
voting_start: timestamp.clone(),
|
||||||
|
voting_end: timestamp.add_secs(voting_duration_secs),
|
||||||
|
quorum_bps: self.default_quorum_bps,
|
||||||
|
pass_threshold_bps: self.default_pass_threshold_bps,
|
||||||
|
constitutional_receipt, created_at: timestamp.clone(), executed_at: None,
|
||||||
|
};
|
||||||
|
self.proposals.insert(proposal_id, proposal);
|
||||||
|
self.pending_events.push(GovernanceProtocolEvent::ProposalCreated { proposal_id, proposer, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(proposal_id)
|
||||||
|
}
|
||||||
|
pub fn cast_vote(
|
||||||
|
&mut self, proposal_id: Hash, voter: Address, choice: VoteChoice, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCGovernanceError> {
|
||||||
|
let voting_power = self.voting_power_registry.get(&voter).copied().unwrap_or(0);
|
||||||
|
if voting_power == 0 {
|
||||||
|
return Err(ACCGovernanceError::InsufficientVotingPower { required: 1, actual: 0 });
|
||||||
|
}
|
||||||
|
let proposal = self.proposals.get_mut(&proposal_id)
|
||||||
|
.ok_or(ACCGovernanceError::ProposalNotFound(proposal_id))?;
|
||||||
|
if proposal.status != ProposalStatus::Active {
|
||||||
|
return Err(ACCGovernanceError::VotingPeriodEnded(proposal_id));
|
||||||
|
}
|
||||||
|
if proposal.vote_records.contains_key(&voter) {
|
||||||
|
return Err(ACCGovernanceError::AlreadyVoted(voter));
|
||||||
|
}
|
||||||
|
match choice {
|
||||||
|
VoteChoice::For => proposal.votes_for = proposal.votes_for.saturating_add(voting_power),
|
||||||
|
VoteChoice::Against => proposal.votes_against = proposal.votes_against.saturating_add(voting_power),
|
||||||
|
VoteChoice::Abstain => proposal.votes_abstain = proposal.votes_abstain.saturating_add(voting_power),
|
||||||
|
}
|
||||||
|
proposal.vote_records.insert(voter.clone(), choice);
|
||||||
|
self.pending_events.push(GovernanceProtocolEvent::VoteCast { proposal_id, voter, choice, voting_power, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn finalize_proposal(
|
||||||
|
&mut self, proposal_id: Hash, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<bool, ACCGovernanceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCGovernanceError::InvalidConstitutionalReceipt); }
|
||||||
|
let proposal = self.proposals.get_mut(&proposal_id)
|
||||||
|
.ok_or(ACCGovernanceError::ProposalNotFound(proposal_id))?;
|
||||||
|
let total_votes = proposal.votes_for + proposal.votes_against + proposal.votes_abstain;
|
||||||
|
let quorum_required = (self.total_voting_power as u64 * proposal.quorum_bps as u64 / 10000) as u128;
|
||||||
|
if total_votes < quorum_required {
|
||||||
|
proposal.status = ProposalStatus::Rejected;
|
||||||
|
self.pending_events.push(GovernanceProtocolEvent::ProposalRejected {
|
||||||
|
proposal_id, reason: format!("未达法定人数: {} < {}", total_votes, quorum_required), timestamp,
|
||||||
|
});
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let effective_votes = proposal.votes_for + proposal.votes_against;
|
||||||
|
let pass_required = (effective_votes as u64 * proposal.pass_threshold_bps as u64 / 10000) as u128;
|
||||||
|
let passed = proposal.votes_for >= pass_required;
|
||||||
|
if passed {
|
||||||
|
proposal.status = ProposalStatus::Executed;
|
||||||
|
proposal.executed_at = Some(timestamp.clone());
|
||||||
|
self.pending_events.push(GovernanceProtocolEvent::ProposalExecuted { proposal_id, timestamp, constitutional_receipt });
|
||||||
|
} else {
|
||||||
|
proposal.status = ProposalStatus::Rejected;
|
||||||
|
self.pending_events.push(GovernanceProtocolEvent::ProposalRejected {
|
||||||
|
proposal_id, reason: "赞成票未达阈值".to_string(), timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(passed)
|
||||||
|
}
|
||||||
|
pub fn get_proposal(&self, id: &Hash) -> Option<&GovernanceProposal> { self.proposals.get(id) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<GovernanceProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,168 @@
|
||||||
//! ACC协议模块
|
//! acc_insurance - NAC 原生协议实现
|
||||||
|
|
||||||
///! # ACC-Insurance: 保险协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// InsurancePolicy
|
pub enum ACCInsuranceError {
|
||||||
pub struct InsurancePolicy {
|
PolicyNotFound(Hash),
|
||||||
/// policy_id
|
PolicyExpired(Hash),
|
||||||
pub policy_id: Hash,
|
ClaimAlreadyProcessed(Hash),
|
||||||
/// insured_asset
|
InvalidConstitutionalReceipt,
|
||||||
pub insured_asset: Hash,
|
Unauthorized(Address),
|
||||||
/// policy_holder
|
ClaimExceedsCoverage { coverage: u128, claim: u128 },
|
||||||
pub policy_holder: Address,
|
}
|
||||||
/// insurer
|
impl std::fmt::Display for ACCInsuranceError {
|
||||||
pub insurer: Address,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// coverage_amount
|
match self {
|
||||||
pub coverage_amount: u128,
|
Self::PolicyNotFound(h) => write!(f, "保险单不存在: {}", h.to_hex()),
|
||||||
/// premium
|
Self::PolicyExpired(h) => write!(f, "保险单已过期: {}", h.to_hex()),
|
||||||
pub premium: u128,
|
Self::ClaimAlreadyProcessed(h) => write!(f, "理赔已处理: {}", h.to_hex()),
|
||||||
/// start_date
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
pub start_date: Timestamp,
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
/// end_date
|
Self::ClaimExceedsCoverage { coverage, claim } => write!(f, "理赔超额: 保额 {},理赔 {}", coverage, claim),
|
||||||
pub end_date: Timestamp,
|
}
|
||||||
/// policy_type
|
|
||||||
pub policy_type: InsuranceType,
|
|
||||||
/// status
|
|
||||||
pub status: PolicyStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// InsuranceType
|
|
||||||
pub enum InsuranceType {
|
|
||||||
/// PropertyDamage
|
|
||||||
PropertyDamage,
|
|
||||||
/// Theft
|
|
||||||
Theft,
|
|
||||||
/// Fraud
|
|
||||||
Fraud,
|
|
||||||
/// Comprehensive
|
|
||||||
Comprehensive,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// PolicyStatus
|
|
||||||
pub enum PolicyStatus {
|
|
||||||
/// Active
|
|
||||||
Active,
|
|
||||||
/// Expired
|
|
||||||
Expired,
|
|
||||||
/// Claimed
|
|
||||||
Claimed,
|
|
||||||
/// Cancelled
|
|
||||||
Cancelled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InsurancePolicy {
|
|
||||||
/// new
|
|
||||||
pub fn new(insured_asset: Hash, policy_holder: Address, insurer: Address, coverage_amount: u128) -> Self {
|
|
||||||
Self {
|
|
||||||
policy_id: Hash::zero(),
|
|
||||||
insured_asset,
|
|
||||||
policy_holder,
|
|
||||||
insurer,
|
|
||||||
coverage_amount,
|
|
||||||
premium: coverage_amount / 100,
|
|
||||||
start_date: Timestamp::now(),
|
|
||||||
end_date: Timestamp::now().add_secs(365 * 24 * 3600),
|
|
||||||
policy_type: InsuranceType::Comprehensive,
|
|
||||||
status: PolicyStatus::Active,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum InsuranceType { AssetLoss, CustodyRisk, ComplianceRisk, MarketPrice, Comprehensive }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InsurancePolicy {
|
||||||
|
pub policy_id: Hash,
|
||||||
|
pub asset_id: Hash,
|
||||||
|
pub insured: Address,
|
||||||
|
pub insurer: Address,
|
||||||
|
pub insurance_type: InsuranceType,
|
||||||
|
pub coverage_amount: u128,
|
||||||
|
pub claimed_amount: u128,
|
||||||
|
pub premium_rate_bps: u16,
|
||||||
|
pub start_time: Timestamp,
|
||||||
|
pub end_time: Timestamp,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl InsurancePolicy {
|
||||||
|
pub fn is_expired(&self) -> bool { self.end_time.is_expired(0) }
|
||||||
|
pub fn remaining_coverage(&self) -> u128 { self.coverage_amount.saturating_sub(self.claimed_amount) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum ClaimStatus { Pending, UnderReview, Approved, Rejected, Paid }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InsuranceClaim {
|
||||||
|
pub claim_id: Hash,
|
||||||
|
pub policy_id: Hash,
|
||||||
|
pub claimant: Address,
|
||||||
|
pub claim_amount: u128,
|
||||||
|
pub claim_reason: String,
|
||||||
|
pub evidence_hash: Hash,
|
||||||
|
pub status: ClaimStatus,
|
||||||
|
pub submitted_at: Timestamp,
|
||||||
|
pub processed_at: Option<Timestamp>,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum InsuranceProtocolEvent {
|
||||||
|
PolicyIssued { policy_id: Hash, asset_id: Hash, insured: Address, coverage: u128, timestamp: Timestamp },
|
||||||
|
ClaimSubmitted { claim_id: Hash, policy_id: Hash, amount: u128, timestamp: Timestamp },
|
||||||
|
ClaimPaid { claim_id: Hash, amount: u128, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCInsuranceProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub policies: HashMap<Hash, InsurancePolicy>,
|
||||||
|
pub claims: HashMap<Hash, InsuranceClaim>,
|
||||||
|
pub insurance_pool: u128,
|
||||||
|
pub pending_events: Vec<InsuranceProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCInsuranceProtocol {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCInsuranceProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Insurance".to_string(),
|
||||||
|
policies: HashMap::new(), claims: HashMap::new(),
|
||||||
|
insurance_pool: 0, pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(), updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fund_pool(&mut self, amount: u128) { self.insurance_pool = self.insurance_pool.saturating_add(amount); }
|
||||||
|
pub fn issue_policy(
|
||||||
|
&mut self, asset_id: Hash, insured: Address, insurer: Address,
|
||||||
|
insurance_type: InsuranceType, coverage_amount: u128, premium_rate_bps: u16,
|
||||||
|
duration_secs: u64, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCInsuranceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCInsuranceError::InvalidConstitutionalReceipt); }
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(asset_id.as_bytes());
|
||||||
|
data.extend_from_slice(insured.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let policy_id = Hash::sha3_384(&data);
|
||||||
|
let policy = InsurancePolicy {
|
||||||
|
policy_id, asset_id, insured: insured.clone(), insurer, insurance_type,
|
||||||
|
coverage_amount, claimed_amount: 0, premium_rate_bps,
|
||||||
|
start_time: timestamp.clone(),
|
||||||
|
end_time: timestamp.add_secs(duration_secs),
|
||||||
|
is_active: true, constitutional_receipt, created_at: timestamp.clone(),
|
||||||
|
};
|
||||||
|
self.policies.insert(policy_id, policy);
|
||||||
|
self.pending_events.push(InsuranceProtocolEvent::PolicyIssued {
|
||||||
|
policy_id, asset_id, insured, coverage: coverage_amount, timestamp,
|
||||||
|
});
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(policy_id)
|
||||||
|
}
|
||||||
|
pub fn submit_claim(
|
||||||
|
&mut self, policy_id: Hash, claimant: Address, claim_amount: u128,
|
||||||
|
claim_reason: String, evidence_hash: Hash, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCInsuranceError> {
|
||||||
|
let policy = self.policies.get(&policy_id).ok_or(ACCInsuranceError::PolicyNotFound(policy_id))?;
|
||||||
|
if policy.is_expired() { return Err(ACCInsuranceError::PolicyExpired(policy_id)); }
|
||||||
|
if claim_amount > policy.remaining_coverage() {
|
||||||
|
return Err(ACCInsuranceError::ClaimExceedsCoverage { coverage: policy.remaining_coverage(), claim: claim_amount });
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(policy_id.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let claim_id = Hash::sha3_384(&data);
|
||||||
|
let claim = InsuranceClaim {
|
||||||
|
claim_id, policy_id, claimant, claim_amount, claim_reason,
|
||||||
|
evidence_hash, status: ClaimStatus::Pending,
|
||||||
|
submitted_at: timestamp.clone(), processed_at: None, constitutional_receipt,
|
||||||
|
};
|
||||||
|
self.claims.insert(claim_id, claim);
|
||||||
|
self.pending_events.push(InsuranceProtocolEvent::ClaimSubmitted { claim_id, policy_id, amount: claim_amount, timestamp });
|
||||||
|
Ok(claim_id)
|
||||||
|
}
|
||||||
|
pub fn pay_claim(
|
||||||
|
&mut self, claim_id: Hash, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<u128, ACCInsuranceError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCInsuranceError::InvalidConstitutionalReceipt); }
|
||||||
|
let claim = self.claims.get_mut(&claim_id).ok_or(ACCInsuranceError::PolicyNotFound(claim_id))?;
|
||||||
|
if claim.status != ClaimStatus::Pending && claim.status != ClaimStatus::UnderReview {
|
||||||
|
return Err(ACCInsuranceError::ClaimAlreadyProcessed(claim_id));
|
||||||
|
}
|
||||||
|
let amount = claim.claim_amount;
|
||||||
|
let policy_id = claim.policy_id;
|
||||||
|
claim.status = ClaimStatus::Paid;
|
||||||
|
claim.processed_at = Some(timestamp.clone());
|
||||||
|
if let Some(policy) = self.policies.get_mut(&policy_id) {
|
||||||
|
policy.claimed_amount = policy.claimed_amount.saturating_add(amount);
|
||||||
|
}
|
||||||
|
self.insurance_pool = self.insurance_pool.saturating_sub(amount);
|
||||||
|
self.pending_events.push(InsuranceProtocolEvent::ClaimPaid { claim_id, amount, timestamp, constitutional_receipt });
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
pub fn get_policy(&self, id: &Hash) -> Option<&InsurancePolicy> { self.policies.get(id) }
|
||||||
|
pub fn get_claim(&self, id: &Hash) -> Option<&InsuranceClaim> { self.claims.get(id) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<InsuranceProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,119 @@
|
||||||
//! ACC协议模块
|
//! acc_redemption - NAC 原生协议实现
|
||||||
|
//! 从 acc_remaining_protocols.rs 提取
|
||||||
///! # ACC-Redemption: 赎回协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// RedemptionRequest
|
pub enum ACCRedemptionError {
|
||||||
pub struct RedemptionRequest {
|
RedemptionNotFound(Hash),
|
||||||
/// request_id
|
InsufficientRedemptionFund { available: u128, requested: u128 },
|
||||||
pub request_id: Hash,
|
RedemptionWindowClosed,
|
||||||
/// token_holder
|
InvalidConstitutionalReceipt,
|
||||||
pub token_holder: Address,
|
Unauthorized(Address),
|
||||||
/// token_id
|
RedemptionAlreadyProcessed(Hash),
|
||||||
pub token_id: Hash,
|
}
|
||||||
/// token_amount
|
impl std::fmt::Display for ACCRedemptionError {
|
||||||
pub token_amount: u128,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// redemption_value
|
match self {
|
||||||
pub redemption_value: u128,
|
Self::RedemptionNotFound(h) => write!(f, "赎回请求不存在: {}", h.to_hex()),
|
||||||
/// requested_at
|
Self::InsufficientRedemptionFund { available, requested } => write!(f, "赎回资金不足: 可用 {},请求 {}", available, requested),
|
||||||
pub requested_at: Timestamp,
|
Self::RedemptionWindowClosed => write!(f, "赎回窗口已关闭"),
|
||||||
/// processed_at
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
pub processed_at: Option<Timestamp>,
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
/// status
|
Self::RedemptionAlreadyProcessed(h) => write!(f, "赎回请求已处理: {}", h.to_hex()),
|
||||||
pub status: RedemptionStatus,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// RedemptionStatus
|
|
||||||
pub enum RedemptionStatus {
|
|
||||||
/// Pending
|
|
||||||
Pending,
|
|
||||||
/// Approved
|
|
||||||
Approved,
|
|
||||||
/// Rejected
|
|
||||||
Rejected,
|
|
||||||
/// Completed
|
|
||||||
Completed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RedemptionRequest {
|
|
||||||
/// new
|
|
||||||
pub fn new(token_holder: Address, token_id: Hash, token_amount: u128, redemption_value: u128) -> Self {
|
|
||||||
Self {
|
|
||||||
request_id: Hash::zero(),
|
|
||||||
token_holder,
|
|
||||||
token_id,
|
|
||||||
token_amount,
|
|
||||||
redemption_value,
|
|
||||||
requested_at: Timestamp::now(),
|
|
||||||
processed_at: None,
|
|
||||||
status: RedemptionStatus::Pending,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum RedemptionStatus { Pending, Processing, Completed, Rejected, Cancelled }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RedemptionRequest {
|
||||||
|
pub redemption_id: Hash,
|
||||||
|
pub asset_id: Hash,
|
||||||
|
pub redeemer: Address,
|
||||||
|
pub amount: u128,
|
||||||
|
pub redemption_price_xtzh: u128,
|
||||||
|
pub total_redemption_xtzh: u128,
|
||||||
|
pub status: RedemptionStatus,
|
||||||
|
pub requested_at: Timestamp,
|
||||||
|
pub processed_at: Option<Timestamp>,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub rejection_reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum RedemptionProtocolEvent {
|
||||||
|
RedemptionRequested { redemption_id: Hash, asset_id: Hash, redeemer: Address, amount: u128, timestamp: Timestamp },
|
||||||
|
RedemptionCompleted { redemption_id: Hash, total_xtzh: u128, timestamp: Timestamp },
|
||||||
|
RedemptionRejected { redemption_id: Hash, reason: String, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCRedemptionProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub requests: HashMap<Hash, RedemptionRequest>,
|
||||||
|
pub redemption_fund: HashMap<Hash, u128>,
|
||||||
|
pub redemption_window_open: bool,
|
||||||
|
pub pending_events: Vec<RedemptionProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCRedemptionProtocol {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCRedemptionProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Redemption".to_string(),
|
||||||
|
requests: HashMap::new(), redemption_fund: HashMap::new(),
|
||||||
|
redemption_window_open: true, pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(), updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fund_redemption_pool(&mut self, asset_id: Hash, amount_xtzh: u128, constitutional_receipt: Hash) -> Result<(), ACCRedemptionError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCRedemptionError::InvalidConstitutionalReceipt); }
|
||||||
|
*self.redemption_fund.entry(asset_id).or_insert(0) += amount_xtzh;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn request_redemption(
|
||||||
|
&mut self, asset_id: Hash, redeemer: Address, amount: u128,
|
||||||
|
redemption_price_xtzh: u128, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCRedemptionError> {
|
||||||
|
if !self.redemption_window_open { return Err(ACCRedemptionError::RedemptionWindowClosed); }
|
||||||
|
let total = amount.saturating_mul(redemption_price_xtzh);
|
||||||
|
let available = self.redemption_fund.get(&asset_id).copied().unwrap_or(0);
|
||||||
|
if available < total { return Err(ACCRedemptionError::InsufficientRedemptionFund { available, requested: total }); }
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(asset_id.as_bytes());
|
||||||
|
data.extend_from_slice(redeemer.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let redemption_id = Hash::sha3_384(&data);
|
||||||
|
let request = RedemptionRequest {
|
||||||
|
redemption_id, asset_id, redeemer: redeemer.clone(), amount,
|
||||||
|
redemption_price_xtzh, total_redemption_xtzh: total,
|
||||||
|
status: RedemptionStatus::Pending,
|
||||||
|
requested_at: timestamp.clone(), processed_at: None,
|
||||||
|
constitutional_receipt, rejection_reason: None,
|
||||||
|
};
|
||||||
|
self.requests.insert(redemption_id, request);
|
||||||
|
self.pending_events.push(RedemptionProtocolEvent::RedemptionRequested { redemption_id, asset_id, redeemer, amount, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(redemption_id)
|
||||||
|
}
|
||||||
|
pub fn complete_redemption(&mut self, redemption_id: Hash, constitutional_receipt: Hash, timestamp: Timestamp) -> Result<u128, ACCRedemptionError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCRedemptionError::InvalidConstitutionalReceipt); }
|
||||||
|
let request = self.requests.get_mut(&redemption_id).ok_or(ACCRedemptionError::RedemptionNotFound(redemption_id))?;
|
||||||
|
if request.status != RedemptionStatus::Pending { return Err(ACCRedemptionError::RedemptionAlreadyProcessed(redemption_id)); }
|
||||||
|
let total = request.total_redemption_xtzh;
|
||||||
|
let asset_id = request.asset_id;
|
||||||
|
if let Some(fund) = self.redemption_fund.get_mut(&asset_id) { *fund = fund.saturating_sub(total); }
|
||||||
|
request.status = RedemptionStatus::Completed;
|
||||||
|
request.processed_at = Some(timestamp.clone());
|
||||||
|
self.pending_events.push(RedemptionProtocolEvent::RedemptionCompleted { redemption_id, total_xtzh: total, timestamp });
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
pub fn get_request(&self, id: &Hash) -> Option<&RedemptionRequest> { self.requests.get(id) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<RedemptionProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,112 @@
|
||||||
//! ACC协议模块
|
//! acc_reserve - NAC 原生协议实现
|
||||||
|
//! 从 acc_remaining_protocols.rs 提取
|
||||||
///! # ACC-Reserve: 储备协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// ReservePool
|
pub enum ACCReserveError {
|
||||||
pub struct ReservePool {
|
ReserveNotFound(String),
|
||||||
/// pool_id
|
InsufficientReserve { asset: String, available: u128, requested: u128 },
|
||||||
pub pool_id: Hash,
|
InvalidConstitutionalReceipt,
|
||||||
/// manager
|
Unauthorized(Address),
|
||||||
pub manager: Address,
|
ReserveRatioViolation { required: u8, actual: u8 },
|
||||||
/// total_value
|
}
|
||||||
pub total_value: u128,
|
impl std::fmt::Display for ACCReserveError {
|
||||||
/// assets
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub assets: Vec<PoolAsset>,
|
match self {
|
||||||
/// target_ratios
|
Self::ReserveNotFound(s) => write!(f, "储备资产不存在: {}", s),
|
||||||
pub target_ratios: std::collections::HashMap<String, u8>,
|
Self::InsufficientReserve { asset, available, requested } => write!(f, "储备不足 {}: 可用 {},请求 {}", asset, available, requested),
|
||||||
/// last_audit
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
pub last_audit: Timestamp,
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
/// audit_frequency
|
Self::ReserveRatioViolation { required, actual } => write!(f, "储备率违规: 要求 {}%,实际 {}%", required, actual),
|
||||||
pub audit_frequency: u64,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// PoolAsset
|
|
||||||
pub struct PoolAsset {
|
|
||||||
/// asset_id
|
|
||||||
pub asset_id: Hash,
|
|
||||||
/// asset_type
|
|
||||||
pub asset_type: String,
|
|
||||||
/// amount
|
|
||||||
pub amount: u128,
|
|
||||||
/// current_value
|
|
||||||
pub current_value: u128,
|
|
||||||
/// custody_provider
|
|
||||||
pub custody_provider: Address,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReservePool {
|
|
||||||
/// new
|
|
||||||
pub fn new(manager: Address) -> Self {
|
|
||||||
Self {
|
|
||||||
pool_id: Hash::zero(),
|
|
||||||
manager,
|
|
||||||
total_value: 0,
|
|
||||||
assets: Vec::new(),
|
|
||||||
target_ratios: std::collections::HashMap::new(),
|
|
||||||
last_audit: Timestamp::now(),
|
|
||||||
audit_frequency: 30 * 24 * 3600,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ReserveEntry {
|
||||||
|
pub asset_symbol: String,
|
||||||
|
pub amount: u128,
|
||||||
|
pub custodian: Address,
|
||||||
|
pub last_audited: Timestamp,
|
||||||
|
pub audit_hash: Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ReserveProtocolEvent {
|
||||||
|
ReserveDeposited { asset: String, amount: u128, custodian: Address, timestamp: Timestamp },
|
||||||
|
ReserveWithdrawn { asset: String, amount: u128, recipient: Address, constitutional_receipt: Hash, timestamp: Timestamp },
|
||||||
|
ReserveAudited { asset: String, audit_hash: Hash, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-Reserve 储备协议
|
||||||
|
/// UID: nac.acc.ACCReserveProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCReserveProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub reserves: HashMap<String, ReserveEntry>,
|
||||||
|
/// 最低储备率(百分比)
|
||||||
|
pub min_reserve_ratio: u8,
|
||||||
|
pub pending_events: Vec<ReserveProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCReserveProtocol {
|
||||||
|
pub fn new(min_reserve_ratio: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCReserveProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Reserve".to_string(),
|
||||||
|
reserves: HashMap::new(),
|
||||||
|
min_reserve_ratio,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn deposit(
|
||||||
|
&mut self, asset_symbol: String, amount: u128, custodian: Address,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCReserveError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCReserveError::InvalidConstitutionalReceipt); }
|
||||||
|
let entry = self.reserves.entry(asset_symbol.clone()).or_insert(ReserveEntry {
|
||||||
|
asset_symbol: asset_symbol.clone(), amount: 0,
|
||||||
|
custodian: custodian.clone(), last_audited: timestamp.clone(),
|
||||||
|
audit_hash: Hash::zero(),
|
||||||
|
});
|
||||||
|
entry.amount = entry.amount.saturating_add(amount);
|
||||||
|
entry.custodian = custodian.clone();
|
||||||
|
self.pending_events.push(ReserveProtocolEvent::ReserveDeposited { asset: asset_symbol, amount, custodian, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn withdraw(
|
||||||
|
&mut self, asset_symbol: String, amount: u128, recipient: Address,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCReserveError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCReserveError::InvalidConstitutionalReceipt); }
|
||||||
|
let entry = self.reserves.get_mut(&asset_symbol)
|
||||||
|
.ok_or_else(|| ACCReserveError::ReserveNotFound(asset_symbol.clone()))?;
|
||||||
|
if entry.amount < amount {
|
||||||
|
return Err(ACCReserveError::InsufficientReserve { asset: asset_symbol.clone(), available: entry.amount, requested: amount });
|
||||||
|
}
|
||||||
|
entry.amount -= amount;
|
||||||
|
self.pending_events.push(ReserveProtocolEvent::ReserveWithdrawn { asset: asset_symbol, amount, recipient, constitutional_receipt, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn audit(
|
||||||
|
&mut self, asset_symbol: String, audit_hash: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCReserveError> {
|
||||||
|
let entry = self.reserves.get_mut(&asset_symbol)
|
||||||
|
.ok_or_else(|| ACCReserveError::ReserveNotFound(asset_symbol.clone()))?;
|
||||||
|
entry.last_audited = timestamp.clone();
|
||||||
|
entry.audit_hash = audit_hash;
|
||||||
|
self.pending_events.push(ReserveProtocolEvent::ReserveAudited { asset: asset_symbol, audit_hash, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get_reserve(&self, asset: &str) -> Option<&ReserveEntry> { self.reserves.get(asset) }
|
||||||
|
pub fn total_reserve_count(&self) -> usize { self.reserves.len() }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<ReserveProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,177 @@
|
||||||
//! ACC协议模块
|
//! acc_rwa - NAC 原生协议实现
|
||||||
|
//! 从 acc_remaining_protocols.rs 提取
|
||||||
///! # ACC-RWA: RWA资产标准
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use crate::l1_protocol::gnacs::GNACSCode;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::{Deserialize, Serialize};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// ACCRWAAsset
|
pub enum ACCRWAError {
|
||||||
pub struct ACCRWAAsset {
|
AssetNotFound(Hash),
|
||||||
/// asset_id
|
AssetAlreadyRegistered(Hash),
|
||||||
pub asset_id: Hash,
|
InvalidConstitutionalReceipt,
|
||||||
/// gnacs_code
|
Unauthorized(Address),
|
||||||
pub gnacs_code: GNACSCode,
|
ComplianceCheckFailed(String),
|
||||||
/// owner
|
ValuationExpired(Hash),
|
||||||
pub owner: Address,
|
AssetFrozen(Hash),
|
||||||
/// valuation
|
}
|
||||||
pub valuation: u128,
|
impl std::fmt::Display for ACCRWAError {
|
||||||
/// custody_provider
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub custody_provider: Address,
|
match self {
|
||||||
/// legal_document_hash
|
Self::AssetNotFound(h) => write!(f, "RWA 资产不存在: {}", h.to_hex()),
|
||||||
pub legal_document_hash: Hash,
|
Self::AssetAlreadyRegistered(h) => write!(f, "RWA 资产已注册: {}", h.to_hex()),
|
||||||
/// created_at
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
pub created_at: Timestamp,
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
/// last_updated
|
Self::ComplianceCheckFailed(msg) => write!(f, "合规检查失败: {}", msg),
|
||||||
pub last_updated: Timestamp,
|
Self::ValuationExpired(h) => write!(f, "估值已过期: {}", h.to_hex()),
|
||||||
}
|
Self::AssetFrozen(h) => write!(f, "资产已冻结: {}", h.to_hex()),
|
||||||
|
}
|
||||||
impl ACCRWAAsset {
|
|
||||||
/// new
|
|
||||||
pub fn new(gnacs_code: GNACSCode, owner: Address, valuation: u128) -> Self {
|
|
||||||
Self {
|
|
||||||
asset_id: Hash::zero(),
|
|
||||||
gnacs_code,
|
|
||||||
owner,
|
|
||||||
valuation,
|
|
||||||
custody_provider: Address::zero(),
|
|
||||||
legal_document_hash: Hash::zero(),
|
|
||||||
created_at: Timestamp::now(),
|
|
||||||
last_updated: Timestamp::now(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// update_valuation
|
|
||||||
pub fn update_valuation(&mut self, new_valuation: u128) {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
self.valuation = new_valuation;
|
pub enum RWAAssetType {
|
||||||
self.last_updated = Timestamp::now();
|
RealEstate,
|
||||||
|
CorporateBond,
|
||||||
|
GovernmentBond,
|
||||||
|
Commodity,
|
||||||
|
PrivateEquity,
|
||||||
|
Infrastructure,
|
||||||
|
IntellectualProperty,
|
||||||
|
NaturalResource,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum RWAAssetStatus { Pending, Active, Frozen, Redeemed, Delisted }
|
||||||
|
|
||||||
|
/// RWA 资产记录
|
||||||
|
/// UID: nac.acc.RWAAssetRecord.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RWAAssetRecord {
|
||||||
|
pub asset_id: Hash,
|
||||||
|
pub gnacs_code: String,
|
||||||
|
pub asset_type: RWAAssetType,
|
||||||
|
pub owner: Address,
|
||||||
|
pub issuer: Address,
|
||||||
|
pub total_supply: u128,
|
||||||
|
pub current_valuation_xtzh: u128,
|
||||||
|
pub jurisdiction: String,
|
||||||
|
pub status: RWAAssetStatus,
|
||||||
|
/// 法律文件哈希(SHA3-384)
|
||||||
|
pub legal_document_hash: Hash,
|
||||||
|
/// AI 合规评分(0-100)
|
||||||
|
pub ai_compliance_score: u8,
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub registered_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum RWAProtocolEvent {
|
||||||
|
AssetRegistered { asset_id: Hash, gnacs_code: String, owner: Address, valuation: u128, timestamp: Timestamp },
|
||||||
|
AssetTransferred { asset_id: Hash, from: Address, to: Address, amount: u128, timestamp: Timestamp },
|
||||||
|
AssetFrozen { asset_id: Hash, reason: String, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
AssetUnfrozen { asset_id: Hash, timestamp: Timestamp, constitutional_receipt: Hash },
|
||||||
|
ValuationUpdated { asset_id: Hash, old_value: u128, new_value: u128, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-RWA 真实世界资产协议
|
||||||
|
/// UID: nac.acc.ACCRWAProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCRWAProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub assets: HashMap<Hash, RWAAssetRecord>,
|
||||||
|
/// 持仓(asset_id -> (address -> amount))
|
||||||
|
pub holdings: HashMap<Hash, HashMap<Address, u128>>,
|
||||||
|
pub pending_events: Vec<RWAProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl ACCRWAProtocol {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCRWAProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-RWA".to_string(),
|
||||||
|
assets: HashMap::new(),
|
||||||
|
holdings: HashMap::new(),
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn register_asset(
|
||||||
|
&mut self, gnacs_code: String, asset_type: RWAAssetType, owner: Address, issuer: Address,
|
||||||
|
total_supply: u128, initial_valuation_xtzh: u128, jurisdiction: String,
|
||||||
|
legal_document_hash: Hash, ai_compliance_score: u8,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCRWAError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCRWAError::InvalidConstitutionalReceipt); }
|
||||||
|
if ai_compliance_score < 60 {
|
||||||
|
return Err(ACCRWAError::ComplianceCheckFailed(format!("AI 合规评分不足: {}/100", ai_compliance_score)));
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(gnacs_code.as_bytes());
|
||||||
|
data.extend_from_slice(owner.as_bytes());
|
||||||
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let asset_id = Hash::sha3_384(&data);
|
||||||
|
if self.assets.contains_key(&asset_id) { return Err(ACCRWAError::AssetAlreadyRegistered(asset_id)); }
|
||||||
|
let record = RWAAssetRecord {
|
||||||
|
asset_id, gnacs_code: gnacs_code.clone(), asset_type, owner: owner.clone(), issuer,
|
||||||
|
total_supply, current_valuation_xtzh: initial_valuation_xtzh,
|
||||||
|
jurisdiction, status: RWAAssetStatus::Active,
|
||||||
|
legal_document_hash, ai_compliance_score, constitutional_receipt,
|
||||||
|
registered_at: timestamp.clone(), updated_at: timestamp.clone(),
|
||||||
|
};
|
||||||
|
let mut asset_holdings = HashMap::new();
|
||||||
|
asset_holdings.insert(owner.clone(), total_supply);
|
||||||
|
self.holdings.insert(asset_id, asset_holdings);
|
||||||
|
self.assets.insert(asset_id, record);
|
||||||
|
self.pending_events.push(RWAProtocolEvent::AssetRegistered {
|
||||||
|
asset_id, gnacs_code, owner, valuation: initial_valuation_xtzh, timestamp,
|
||||||
|
});
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(asset_id)
|
||||||
|
}
|
||||||
|
pub fn transfer_asset(
|
||||||
|
&mut self, asset_id: Hash, from: Address, to: Address, amount: u128, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCRWAError> {
|
||||||
|
let asset = self.assets.get(&asset_id).ok_or(ACCRWAError::AssetNotFound(asset_id))?;
|
||||||
|
if asset.status == RWAAssetStatus::Frozen { return Err(ACCRWAError::AssetFrozen(asset_id)); }
|
||||||
|
let holdings = self.holdings.get_mut(&asset_id).ok_or(ACCRWAError::AssetNotFound(asset_id))?;
|
||||||
|
let from_balance = holdings.get(&from).copied().unwrap_or(0);
|
||||||
|
if from_balance < amount {
|
||||||
|
return Err(ACCRWAError::Unauthorized(from.clone()));
|
||||||
|
}
|
||||||
|
*holdings.get_mut(&from).unwrap() -= amount;
|
||||||
|
*holdings.entry(to.clone()).or_insert(0) += amount;
|
||||||
|
self.pending_events.push(RWAProtocolEvent::AssetTransferred { asset_id, from, to, amount, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn freeze_asset(
|
||||||
|
&mut self, asset_id: Hash, reason: String, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCRWAError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCRWAError::InvalidConstitutionalReceipt); }
|
||||||
|
let asset = self.assets.get_mut(&asset_id).ok_or(ACCRWAError::AssetNotFound(asset_id))?;
|
||||||
|
asset.status = RWAAssetStatus::Frozen;
|
||||||
|
asset.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(RWAProtocolEvent::AssetFrozen { asset_id, reason, timestamp, constitutional_receipt });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn update_valuation(
|
||||||
|
&mut self, asset_id: Hash, new_valuation: u128, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCRWAError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCRWAError::InvalidConstitutionalReceipt); }
|
||||||
|
let asset = self.assets.get_mut(&asset_id).ok_or(ACCRWAError::AssetNotFound(asset_id))?;
|
||||||
|
let old_value = asset.current_valuation_xtzh;
|
||||||
|
asset.current_valuation_xtzh = new_valuation;
|
||||||
|
asset.updated_at = timestamp.clone();
|
||||||
|
self.pending_events.push(RWAProtocolEvent::ValuationUpdated { asset_id, old_value, new_value: new_valuation, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get_asset(&self, id: &Hash) -> Option<&RWAAssetRecord> { self.assets.get(id) }
|
||||||
|
pub fn balance_of(&self, asset_id: &Hash, holder: &Address) -> u128 {
|
||||||
|
self.holdings.get(asset_id).and_then(|h| h.get(holder)).copied().unwrap_or(0)
|
||||||
|
}
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<RWAProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,294 @@
|
||||||
//! ACC协议模块
|
//! ACC-Valuation: 资产估值协议
|
||||||
|
//! UID: nac.acc.ACCValuationProtocol.v1
|
||||||
|
//!
|
||||||
|
//! 完全规避以太坊模式,使用 NAC 原生类型系统:
|
||||||
|
//! - Address: 32字节
|
||||||
|
//! - Hash: 48字节(SHA3-384)
|
||||||
|
//! - 通过 CBPP 宪法收据驱动状态变更
|
||||||
|
|
||||||
///! # ACC-Valuation: 估值协议
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
/// ACC-Valuation 错误类型
|
||||||
/// ValuationReport
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ValuationReport {
|
pub enum ACCValuationError {
|
||||||
/// asset_id
|
AssetNotFound(Hash),
|
||||||
pub asset_id: Hash,
|
ValuationNotFound(Hash),
|
||||||
/// valuation_amount
|
UnauthorizedAppraiser(Address),
|
||||||
pub valuation_amount: u128,
|
ValuationExpired(Hash),
|
||||||
/// currency
|
InvalidValuationAmount,
|
||||||
pub currency: String,
|
InsufficientAIConfidence { actual: u8, required: u8 },
|
||||||
/// valuation_method
|
InvalidConstitutionalReceipt,
|
||||||
pub valuation_method: String,
|
SDRConversionFailed(String),
|
||||||
/// appraiser
|
|
||||||
pub appraiser: Address,
|
|
||||||
/// report_date
|
|
||||||
pub report_date: Timestamp,
|
|
||||||
/// validity_period
|
|
||||||
pub validity_period: u64,
|
|
||||||
/// confidence_score
|
|
||||||
pub confidence_score: u8,
|
|
||||||
/// report_hash
|
|
||||||
pub report_hash: Hash,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValuationReport {
|
impl std::fmt::Display for ACCValuationError {
|
||||||
/// new
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub fn new(asset_id: Hash, valuation_amount: u128, appraiser: Address) -> Self {
|
match self {
|
||||||
Self {
|
Self::AssetNotFound(h) => write!(f, "资产不存在: {}", h.to_hex()),
|
||||||
asset_id,
|
Self::ValuationNotFound(h) => write!(f, "估值记录不存在: {}", h.to_hex()),
|
||||||
valuation_amount,
|
Self::UnauthorizedAppraiser(a) => write!(f, "未授权估值机构: {}", a.to_hex()),
|
||||||
currency: "USD".to_string(),
|
Self::ValuationExpired(h) => write!(f, "估值已过期: {}", h.to_hex()),
|
||||||
valuation_method: "Market Approach".to_string(),
|
Self::InvalidValuationAmount => write!(f, "估值金额无效"),
|
||||||
appraiser,
|
Self::InsufficientAIConfidence { actual, required } => {
|
||||||
report_date: Timestamp::now(),
|
write!(f, "AI置信度不足: 实际 {},要求 {}", actual, required)
|
||||||
validity_period: 180 * 24 * 3600,
|
}
|
||||||
confidence_score: 80,
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
report_hash: Hash::zero(),
|
Self::SDRConversionFailed(msg) => write!(f, "SDR转换失败: {}", msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// is_valid
|
}
|
||||||
|
|
||||||
|
/// 估值方法(NAC 原生)
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum ValuationMethod {
|
||||||
|
/// XTZH-AI-Engine 智能估值
|
||||||
|
XTZHAIEngine,
|
||||||
|
/// 市场比较法
|
||||||
|
MarketApproach,
|
||||||
|
/// 收益法
|
||||||
|
IncomeApproach,
|
||||||
|
/// 成本法
|
||||||
|
CostApproach,
|
||||||
|
/// SDR 锚定估值
|
||||||
|
SDRAnchoredValuation,
|
||||||
|
/// 混合估值
|
||||||
|
HybridApproach,
|
||||||
|
/// 独立第三方估值
|
||||||
|
IndependentThirdParty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 估值记录
|
||||||
|
/// UID: nac.acc.ValuationRecord.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ValuationRecord {
|
||||||
|
pub valuation_id: Hash,
|
||||||
|
pub asset_id: Hash,
|
||||||
|
/// 估值金额(XTZH,精度18位)
|
||||||
|
pub amount_xtzh: u128,
|
||||||
|
/// 估值金额(SDR)
|
||||||
|
pub amount_sdr: u128,
|
||||||
|
pub method: ValuationMethod,
|
||||||
|
pub appraiser: Address,
|
||||||
|
/// AI 置信度(0-100)
|
||||||
|
pub ai_confidence: u8,
|
||||||
|
pub valuation_time: Timestamp,
|
||||||
|
/// 有效期(秒)
|
||||||
|
pub validity_secs: u64,
|
||||||
|
/// 估值报告哈希(SHA3-384,48字节)
|
||||||
|
pub report_hash: Hash,
|
||||||
|
/// 宪法收据哈希
|
||||||
|
pub constitutional_receipt: Hash,
|
||||||
|
pub is_current: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValuationRecord {
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
!self.report_date.add_secs(self.validity_period).is_expired(Timestamp::now().as_secs())
|
self.is_current && !self.valuation_time.is_expired(self.validity_secs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 估值协议事件
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ValuationProtocolEvent {
|
||||||
|
ValuationUpdated {
|
||||||
|
asset_id: Hash,
|
||||||
|
valuation_id: Hash,
|
||||||
|
old_value_xtzh: u128,
|
||||||
|
new_value_xtzh: u128,
|
||||||
|
method: ValuationMethod,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
},
|
||||||
|
AppraiserAuthorized {
|
||||||
|
appraiser: Address,
|
||||||
|
authorized_by: Address,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
},
|
||||||
|
AppraiserRevoked {
|
||||||
|
appraiser: Address,
|
||||||
|
revoked_by: Address,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ACC-Valuation 估值协议
|
||||||
|
/// UID: nac.acc.ACCValuationProtocol.v1
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ACCValuationProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
/// 当前估值注册表(asset_id -> 最新估值)
|
||||||
|
pub current_valuations: HashMap<Hash, ValuationRecord>,
|
||||||
|
/// 历史估值
|
||||||
|
pub valuation_history: HashMap<Hash, Vec<ValuationRecord>>,
|
||||||
|
/// 授权估值机构
|
||||||
|
pub authorized_appraisers: HashMap<Address, Timestamp>,
|
||||||
|
/// AI 置信度阈值(0-100)
|
||||||
|
pub ai_confidence_threshold: u8,
|
||||||
|
/// SDR 汇率(XTZH/SDR,精度18位)
|
||||||
|
pub sdr_exchange_rate: u128,
|
||||||
|
pub pending_events: Vec<ValuationProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ACCValuationProtocol {
|
||||||
|
pub fn new(ai_confidence_threshold: u8, sdr_exchange_rate: u128) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.ACCValuationProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-Valuation".to_string(),
|
||||||
|
current_valuations: HashMap::new(),
|
||||||
|
valuation_history: HashMap::new(),
|
||||||
|
authorized_appraisers: HashMap::new(),
|
||||||
|
ai_confidence_threshold,
|
||||||
|
sdr_exchange_rate,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 授权估值机构
|
||||||
|
pub fn authorize_appraiser(
|
||||||
|
&mut self,
|
||||||
|
appraiser: Address,
|
||||||
|
authorized_by: Address,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCValuationError> {
|
||||||
|
if constitutional_receipt.is_zero() {
|
||||||
|
return Err(ACCValuationError::InvalidConstitutionalReceipt);
|
||||||
|
}
|
||||||
|
self.authorized_appraisers.insert(appraiser.clone(), timestamp.clone());
|
||||||
|
self.pending_events.push(ValuationProtocolEvent::AppraiserAuthorized {
|
||||||
|
appraiser,
|
||||||
|
authorized_by,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 提交资产估值
|
||||||
|
pub fn submit_valuation(
|
||||||
|
&mut self,
|
||||||
|
asset_id: Hash,
|
||||||
|
amount_xtzh: u128,
|
||||||
|
method: ValuationMethod,
|
||||||
|
appraiser: Address,
|
||||||
|
ai_confidence: u8,
|
||||||
|
report_hash: Hash,
|
||||||
|
constitutional_receipt: Hash,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<Hash, ACCValuationError> {
|
||||||
|
if !self.authorized_appraisers.contains_key(&appraiser) {
|
||||||
|
return Err(ACCValuationError::UnauthorizedAppraiser(appraiser));
|
||||||
|
}
|
||||||
|
if ai_confidence < self.ai_confidence_threshold {
|
||||||
|
return Err(ACCValuationError::InsufficientAIConfidence {
|
||||||
|
actual: ai_confidence,
|
||||||
|
required: self.ai_confidence_threshold,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if amount_xtzh == 0 {
|
||||||
|
return Err(ACCValuationError::InvalidValuationAmount);
|
||||||
|
}
|
||||||
|
let amount_sdr = if self.sdr_exchange_rate > 0 {
|
||||||
|
amount_xtzh / self.sdr_exchange_rate
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let old_value = self.current_valuations.get(&asset_id)
|
||||||
|
.map(|v| v.amount_xtzh)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let mut val_data = Vec::new();
|
||||||
|
val_data.extend_from_slice(asset_id.as_bytes());
|
||||||
|
val_data.extend_from_slice(&amount_xtzh.to_be_bytes());
|
||||||
|
val_data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||||
|
let valuation_id = Hash::sha3_384(&val_data);
|
||||||
|
if let Some(old_val) = self.current_valuations.get_mut(&asset_id) {
|
||||||
|
old_val.is_current = false;
|
||||||
|
}
|
||||||
|
let record = ValuationRecord {
|
||||||
|
valuation_id,
|
||||||
|
asset_id,
|
||||||
|
amount_xtzh,
|
||||||
|
amount_sdr,
|
||||||
|
method,
|
||||||
|
appraiser: appraiser.clone(),
|
||||||
|
ai_confidence,
|
||||||
|
valuation_time: timestamp.clone(),
|
||||||
|
validity_secs: 180 * 24 * 3600,
|
||||||
|
report_hash,
|
||||||
|
constitutional_receipt,
|
||||||
|
is_current: true,
|
||||||
|
};
|
||||||
|
self.valuation_history
|
||||||
|
.entry(asset_id)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(record.clone());
|
||||||
|
self.current_valuations.insert(asset_id, record);
|
||||||
|
self.pending_events.push(ValuationProtocolEvent::ValuationUpdated {
|
||||||
|
asset_id,
|
||||||
|
valuation_id,
|
||||||
|
old_value_xtzh: old_value,
|
||||||
|
new_value_xtzh: amount_xtzh,
|
||||||
|
method,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(valuation_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询资产当前估值
|
||||||
|
pub fn get_current_valuation(&self, asset_id: &Hash) -> Option<&ValuationRecord> {
|
||||||
|
self.current_valuations.get(asset_id)
|
||||||
|
.filter(|v| v.is_valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询估值历史
|
||||||
|
pub fn get_valuation_history(&self, asset_id: &Hash) -> &[ValuationRecord] {
|
||||||
|
self.valuation_history.get(asset_id)
|
||||||
|
.map(|v| v.as_slice())
|
||||||
|
.unwrap_or(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<ValuationProtocolEvent> {
|
||||||
|
std::mem::take(&mut self.pending_events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_submit_valuation() {
|
||||||
|
let mut proto = ACCValuationProtocol::new(70, 1_000_000_000_000_000_000u128);
|
||||||
|
let appraiser = Address::new([1u8; 32]);
|
||||||
|
let asset_id = Hash::sha3_384(b"asset1");
|
||||||
|
let receipt = Hash::sha3_384(b"receipt");
|
||||||
|
let ts = Timestamp::now();
|
||||||
|
|
||||||
|
proto.authorize_appraiser(
|
||||||
|
appraiser.clone(),
|
||||||
|
Address::new([9u8; 32]),
|
||||||
|
receipt.clone(),
|
||||||
|
ts.clone(),
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let vid = proto.submit_valuation(
|
||||||
|
asset_id,
|
||||||
|
1_000_000 * 10u128.pow(18),
|
||||||
|
ValuationMethod::XTZHAIEngine,
|
||||||
|
appraiser,
|
||||||
|
85,
|
||||||
|
Hash::sha3_384(b"report"),
|
||||||
|
receipt,
|
||||||
|
ts,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert!(!vid.is_zero());
|
||||||
|
assert!(proto.get_current_valuation(&asset_id).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,172 @@
|
||||||
//! ACC协议模块
|
//! acc_xtzh - NAC 原生协议实现
|
||||||
|
//! 从 acc_remaining_protocols.rs 提取
|
||||||
///! # ACC-XTZH: XTZH稳定币协议
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use crate::primitives::{Address, Hash, Timestamp};
|
use crate::primitives::{Address, Hash, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
/// XTZHToken
|
pub enum ACCXTZHError {
|
||||||
pub struct XTZHToken {
|
InsufficientBalance { holder: Address, available: u128, requested: u128 },
|
||||||
/// total_supply
|
InsufficientReserve { required: u128, available: u128 },
|
||||||
pub total_supply: u128,
|
InvalidConstitutionalReceipt,
|
||||||
/// sdr_peg_rate
|
Unauthorized(Address),
|
||||||
pub sdr_peg_rate: u128,
|
SDRPegViolation { current_rate: u128, min_rate: u128, max_rate: u128 },
|
||||||
/// gold_reserve_ratio
|
GoldReserveInsufficient { required_ratio: u8, actual_ratio: u8 },
|
||||||
pub gold_reserve_ratio: u8,
|
}
|
||||||
/// balances
|
impl std::fmt::Display for ACCXTZHError {
|
||||||
pub balances: std::collections::HashMap<Address, u128>,
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
/// reserve_assets
|
match self {
|
||||||
pub reserve_assets: Vec<ReserveAsset>,
|
Self::InsufficientBalance { holder, available, requested } => write!(f, "余额不足 {}: 可用 {},请求 {}", holder.to_hex(), available, requested),
|
||||||
/// last_rebalance
|
Self::InsufficientReserve { required, available } => write!(f, "储备不足: 需要 {},可用 {}", required, available),
|
||||||
pub last_rebalance: Timestamp,
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||||||
}
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
||||||
|
Self::SDRPegViolation { current_rate, min_rate, max_rate } => write!(f, "SDR 锚定偏离: 当前 {},允许范围 [{}, {}]", current_rate, min_rate, max_rate),
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
Self::GoldReserveInsufficient { required_ratio, actual_ratio } => write!(f, "黄金储备不足: 要求 {}%,实际 {}%", required_ratio, actual_ratio),
|
||||||
/// ReserveAsset
|
}
|
||||||
pub struct ReserveAsset {
|
|
||||||
/// asset_type
|
|
||||||
pub asset_type: ReserveAssetType,
|
|
||||||
/// amount
|
|
||||||
pub amount: u128,
|
|
||||||
/// weight
|
|
||||||
pub weight: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
/// ReserveAssetType
|
|
||||||
pub enum ReserveAssetType {
|
|
||||||
/// Gold
|
|
||||||
Gold,
|
|
||||||
/// USD
|
|
||||||
USD,
|
|
||||||
/// EUR
|
|
||||||
EUR,
|
|
||||||
/// GBP
|
|
||||||
GBP,
|
|
||||||
/// JPY
|
|
||||||
JPY,
|
|
||||||
/// CNY
|
|
||||||
CNY,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XTZHToken {
|
|
||||||
/// new
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
total_supply: 0,
|
|
||||||
sdr_peg_rate: 100_000_000,
|
|
||||||
gold_reserve_ratio: 40,
|
|
||||||
balances: std::collections::HashMap::new(),
|
|
||||||
reserve_assets: Vec::new(),
|
|
||||||
last_rebalance: Timestamp::now(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum ReserveAssetType { Gold, USD, EUR, GBP, JPY, CNY, NACNative }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ReserveAsset {
|
||||||
|
pub asset_type: ReserveAssetType,
|
||||||
|
pub amount: u128,
|
||||||
|
/// 权重(基点,10000=100%)
|
||||||
|
pub weight_bps: u16,
|
||||||
|
pub last_updated: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum XTZHProtocolEvent {
|
||||||
|
Minted { recipient: Address, amount: u128, constitutional_receipt: Hash, timestamp: Timestamp },
|
||||||
|
Burned { holder: Address, amount: u128, constitutional_receipt: Hash, timestamp: Timestamp },
|
||||||
|
Transferred { from: Address, to: Address, amount: u128, timestamp: Timestamp },
|
||||||
|
ReserveRebalanced { old_gold_ratio: u8, new_gold_ratio: u8, timestamp: Timestamp },
|
||||||
|
SDRRateUpdated { old_rate: u128, new_rate: u128, timestamp: Timestamp },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XTZH 稳定币协议
|
||||||
|
/// UID: nac.acc.XTZHStablecoinProtocol.v1
|
||||||
|
/// SDR 锚定 + 黄金储备保障
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct XTZHStablecoinProtocol {
|
||||||
|
pub protocol_uid: String,
|
||||||
|
pub lens_protocol_vector: String,
|
||||||
|
pub total_supply: u128,
|
||||||
|
/// 持仓(address -> 余额,精度18位)
|
||||||
|
pub holdings: HashMap<Address, u128>,
|
||||||
|
/// 储备资产
|
||||||
|
pub reserve_assets: Vec<ReserveAsset>,
|
||||||
|
/// SDR 锚定汇率(XTZH/SDR,精度18位,1 XTZH = 1 SDR)
|
||||||
|
pub sdr_peg_rate: u128,
|
||||||
|
/// SDR 汇率允许偏差(基点,默认200=2%)
|
||||||
|
pub sdr_tolerance_bps: u16,
|
||||||
|
/// 黄金储备最低比例(百分比,默认40)
|
||||||
|
pub min_gold_reserve_ratio: u8,
|
||||||
|
/// 当前黄金储备比例
|
||||||
|
pub current_gold_reserve_ratio: u8,
|
||||||
|
pub pending_events: Vec<XTZHProtocolEvent>,
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
pub updated_at: Timestamp,
|
||||||
|
}
|
||||||
|
impl XTZHStablecoinProtocol {
|
||||||
|
pub fn new(sdr_peg_rate: u128, min_gold_reserve_ratio: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
protocol_uid: "nac.acc.XTZHStablecoinProtocol.v1".to_string(),
|
||||||
|
lens_protocol_vector: "ACC-XTZH".to_string(),
|
||||||
|
total_supply: 0,
|
||||||
|
holdings: HashMap::new(),
|
||||||
|
reserve_assets: Vec::new(),
|
||||||
|
sdr_peg_rate,
|
||||||
|
sdr_tolerance_bps: 200,
|
||||||
|
min_gold_reserve_ratio,
|
||||||
|
current_gold_reserve_ratio: 0,
|
||||||
|
pending_events: Vec::new(),
|
||||||
|
created_at: Timestamp::now(),
|
||||||
|
updated_at: Timestamp::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn mint(
|
||||||
|
&mut self, recipient: Address, amount: u128,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCXTZHError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCXTZHError::InvalidConstitutionalReceipt); }
|
||||||
|
if self.current_gold_reserve_ratio < self.min_gold_reserve_ratio {
|
||||||
|
return Err(ACCXTZHError::GoldReserveInsufficient {
|
||||||
|
required_ratio: self.min_gold_reserve_ratio,
|
||||||
|
actual_ratio: self.current_gold_reserve_ratio,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*self.holdings.entry(recipient.clone()).or_insert(0) += amount;
|
||||||
|
self.total_supply = self.total_supply.saturating_add(amount);
|
||||||
|
self.pending_events.push(XTZHProtocolEvent::Minted { recipient, amount, constitutional_receipt, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn burn(
|
||||||
|
&mut self, holder: Address, amount: u128,
|
||||||
|
constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCXTZHError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCXTZHError::InvalidConstitutionalReceipt); }
|
||||||
|
let balance = self.holdings.get(&holder).copied().unwrap_or(0);
|
||||||
|
if balance < amount {
|
||||||
|
return Err(ACCXTZHError::InsufficientBalance { holder: holder.clone(), available: balance, requested: amount });
|
||||||
|
}
|
||||||
|
*self.holdings.get_mut(&holder).unwrap() -= amount;
|
||||||
|
self.total_supply = self.total_supply.saturating_sub(amount);
|
||||||
|
self.pending_events.push(XTZHProtocolEvent::Burned { holder, amount, constitutional_receipt, timestamp });
|
||||||
|
self.updated_at = Timestamp::now();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn transfer(
|
||||||
|
&mut self, from: Address, to: Address, amount: u128, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCXTZHError> {
|
||||||
|
let balance = self.holdings.get(&from).copied().unwrap_or(0);
|
||||||
|
if balance < amount {
|
||||||
|
return Err(ACCXTZHError::InsufficientBalance { holder: from.clone(), available: balance, requested: amount });
|
||||||
|
}
|
||||||
|
*self.holdings.get_mut(&from).unwrap() -= amount;
|
||||||
|
*self.holdings.entry(to.clone()).or_insert(0) += amount;
|
||||||
|
self.pending_events.push(XTZHProtocolEvent::Transferred { from, to, amount, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn update_sdr_rate(
|
||||||
|
&mut self, new_rate: u128, constitutional_receipt: Hash, timestamp: Timestamp,
|
||||||
|
) -> Result<(), ACCXTZHError> {
|
||||||
|
if constitutional_receipt.is_zero() { return Err(ACCXTZHError::InvalidConstitutionalReceipt); }
|
||||||
|
let tolerance = self.sdr_peg_rate * self.sdr_tolerance_bps as u128 / 10000;
|
||||||
|
let min_rate = self.sdr_peg_rate.saturating_sub(tolerance);
|
||||||
|
let max_rate = self.sdr_peg_rate.saturating_add(tolerance);
|
||||||
|
if new_rate < min_rate || new_rate > max_rate {
|
||||||
|
return Err(ACCXTZHError::SDRPegViolation { current_rate: new_rate, min_rate, max_rate });
|
||||||
|
}
|
||||||
|
let old_rate = self.sdr_peg_rate;
|
||||||
|
self.sdr_peg_rate = new_rate;
|
||||||
|
self.pending_events.push(XTZHProtocolEvent::SDRRateUpdated { old_rate, new_rate, timestamp });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn update_reserve(
|
||||||
|
&mut self, asset_type: ReserveAssetType, amount: u128, weight_bps: u16, timestamp: Timestamp,
|
||||||
|
) {
|
||||||
|
if let Some(r) = self.reserve_assets.iter_mut().find(|r| r.asset_type == asset_type) {
|
||||||
|
r.amount = amount;
|
||||||
|
r.weight_bps = weight_bps;
|
||||||
|
r.last_updated = timestamp;
|
||||||
|
} else {
|
||||||
|
self.reserve_assets.push(ReserveAsset { asset_type, amount, weight_bps, last_updated: timestamp });
|
||||||
|
}
|
||||||
|
self.recalculate_gold_ratio();
|
||||||
|
}
|
||||||
|
fn recalculate_gold_ratio(&mut self) {
|
||||||
|
let total_weight: u16 = self.reserve_assets.iter().map(|r| r.weight_bps).sum();
|
||||||
|
if total_weight == 0 { self.current_gold_reserve_ratio = 0; return; }
|
||||||
|
let gold_weight: u16 = self.reserve_assets.iter()
|
||||||
|
.filter(|r| r.asset_type == ReserveAssetType::Gold)
|
||||||
|
.map(|r| r.weight_bps).sum();
|
||||||
|
self.current_gold_reserve_ratio = (gold_weight as u32 * 100 / total_weight as u32) as u8;
|
||||||
|
}
|
||||||
|
pub fn balance_of(&self, address: &Address) -> u128 { self.holdings.get(address).copied().unwrap_or(0) }
|
||||||
|
pub fn drain_pending_events(&mut self) -> Vec<XTZHProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,48 @@
|
||||||
//! ACC协议模块
|
//! ACC协议族 (Asset Classification & Compliance Protocol Suite)
|
||||||
|
//!
|
||||||
///! # ACC协议族 (Asset Classification & Compliance Protocol Suite)
|
//! NAC原生公链核心协议标准,完全替代以太坊的ERC标准。
|
||||||
///!
|
//! 所有协议使用 Charter 语言编写,基于 NVM 虚拟机执行。
|
||||||
///! ACC协议族是NAC原生公链的核心协议标准,完全替代以太坊的ERC标准。
|
//!
|
||||||
///!
|
//! ## 完整 ACC 协议族(22个协议)
|
||||||
///! ## 13个ACC协议
|
//!
|
||||||
///!
|
//! ### 基础代币协议(4个)
|
||||||
///! ### 基础代币协议
|
//! - **ACC-20**: 可替代代币标准(Fungible Token)
|
||||||
///! - **ACC-20**: 可替代代币标准(Fungible Token)
|
//! - **ACC-20 Enhanced**: 增强可替代代币(含GNACS/主权/合规/估值/碎片化)
|
||||||
///! - **ACC-721**: 不可替代代币标准(Non-Fungible Token)
|
//! - **ACC-721**: 不可替代代币标准(Non-Fungible Token,含AssetDNA)
|
||||||
///! - **ACC-1155**: 多代币标准(Multi-Token)
|
//! - **ACC-1155**: 多代币标准(Fungible + NFT 混合)
|
||||||
///!
|
//!
|
||||||
///! ### RWA专用协议
|
//! ### RWA 专用协议族(8个)
|
||||||
///! - **ACC-RWA**: RWA资产标准
|
//! - **ACC-RWA**: RWA 真实世界资产注册与转移协议
|
||||||
///! - **ACC-Compliance**: 合规协议
|
//! - **ACC-Compliance**: 七层合规验证协议(KYC/AML/司法管辖区/AI评分/宪法合规)
|
||||||
///! - **ACC-Valuation**: 估值协议
|
//! - **ACC-Valuation**: AI 驱动资产估值协议(CNNL 接口)
|
||||||
///! - **ACC-Custody**: 托管协议
|
//! - **ACC-Custody**: 资产托管协议(多级托管/托管人管理)
|
||||||
///! - **ACC-Collateral**: 抵押协议
|
//! - **ACC-Collateral**: 抵押协议(抵押率/清算阈值/XTZH计价)
|
||||||
///! - **ACC-Redemption**: 赎回协议
|
//! - **ACC-Redemption**: 赎回协议(赎回窗口/赎回资金池)
|
||||||
///! - **ACC-Insurance**: 保险协议
|
//! - **ACC-Insurance**: 资产保险协议(保险单/理赔/保险资金池)
|
||||||
///!
|
//! - **ACC-Governance**: 治理协议(提案/投票/法定人数/执行)
|
||||||
///! ### 新增协议
|
//!
|
||||||
///! - **ACC-1594**: 收益分配协议
|
//! ### 稳定币与储备协议(2个)
|
||||||
///! - **ACC-1643**: 碎片化交易协议
|
//! - **ACC-XTZH**: XTZH 稳定币协议(SDR锚定+黄金储备)
|
||||||
///! - **ACC-1644**: 跨链桥接协议
|
//! - **ACC-Reserve**: 多资产储备协议(审计/存取/储备率)
|
||||||
///! - **ACC-1400**: 托管协议
|
//!
|
||||||
///! - **ACC-1410**: 保险协议
|
//! ### 辅助工具(2个)
|
||||||
///!
|
//! - **ACC-Performance**: 资产性能监控模块
|
||||||
///! ### 治理与稳定币协议
|
//! - **XTZH-AI-Engine**: XTZH AI 稳定性引擎
|
||||||
///! - **ACC-Governance**: 治理协议
|
//!
|
||||||
///! - **ACC-XTZH**: XTZH稳定币协议
|
//! ### 证券代币协议族(5个,符合国际证券代币标准)
|
||||||
///! - **ACC-Reserve**: 储备协议
|
//! - **ACC-1410**: 分区代币协议(股权分区/分红/投票/转让限制)
|
||||||
|
//! - **ACC-1400**: 证券代币协议(合规+股息+投票,继承 ACC-1410)
|
||||||
|
//! - **ACC-1594**: 收益分配协议(发行/赎回/分红,依赖 ACC-1410+1400)
|
||||||
|
//! - **ACC-1643**: 文档管理协议(链上文档版本控制/法律文件哈希)
|
||||||
|
//! - **ACC-1644**: 监管控制协议(冻结/强制转移/监管接管)
|
||||||
|
|
||||||
|
// === 基础代币协议 ===
|
||||||
pub mod acc20;
|
pub mod acc20;
|
||||||
pub mod acc20_enhanced;
|
pub mod acc20_enhanced;
|
||||||
pub mod acc721;
|
pub mod acc721;
|
||||||
pub mod acc1155;
|
pub mod acc1155;
|
||||||
|
|
||||||
|
// === RWA 专用协议族 ===
|
||||||
pub mod acc_rwa;
|
pub mod acc_rwa;
|
||||||
pub mod acc_compliance;
|
pub mod acc_compliance;
|
||||||
pub mod acc_valuation;
|
pub mod acc_valuation;
|
||||||
|
|
@ -44,25 +51,30 @@ pub mod acc_collateral;
|
||||||
pub mod acc_redemption;
|
pub mod acc_redemption;
|
||||||
pub mod acc_insurance;
|
pub mod acc_insurance;
|
||||||
pub mod acc_governance;
|
pub mod acc_governance;
|
||||||
|
|
||||||
|
// === 稳定币与储备协议 ===
|
||||||
pub mod acc_xtzh;
|
pub mod acc_xtzh;
|
||||||
pub mod acc_reserve;
|
pub mod acc_reserve;
|
||||||
/// ACC资产性能监控模块
|
|
||||||
|
// === 辅助工具 ===
|
||||||
|
/// ACC 资产性能监控模块
|
||||||
pub mod acc_performance;
|
pub mod acc_performance;
|
||||||
/// XTZH AI引擎模块
|
/// XTZH AI 稳定性引擎
|
||||||
pub mod xtzh_ai_engine;
|
pub mod xtzh_ai_engine;
|
||||||
|
|
||||||
// 新增协议模块
|
// === 证券代币协议族 ===
|
||||||
/// ACC-1594: 收益分配协议
|
/// ACC-1410: 分区代币协议(股权分区/分红/投票/转让限制)
|
||||||
pub mod acc1594;
|
|
||||||
/// ACC-1643: 碎片化交易协议
|
|
||||||
pub mod acc1643;
|
|
||||||
/// ACC-1644: 跨链桥接协议
|
|
||||||
pub mod acc1644;
|
|
||||||
/// ACC-1400: 托管协议
|
|
||||||
pub mod acc1400;
|
|
||||||
/// ACC-1410: 保险协议
|
|
||||||
pub mod acc1410;
|
pub mod acc1410;
|
||||||
|
/// ACC-1400: 证券代币协议(合规+股息+投票,继承 ACC-1410)
|
||||||
|
pub mod acc1400;
|
||||||
|
/// ACC-1594: 收益分配协议(发行/赎回/分红)
|
||||||
|
pub mod acc1594;
|
||||||
|
/// ACC-1643: 文档管理协议(链上文档版本控制)
|
||||||
|
pub mod acc1643;
|
||||||
|
/// ACC-1644: 监管控制协议(冻结/强制转移/监管接管)
|
||||||
|
pub mod acc1644;
|
||||||
|
|
||||||
|
// === 公开导出 ===
|
||||||
pub use acc20::*;
|
pub use acc20::*;
|
||||||
pub use acc20_enhanced::*;
|
pub use acc20_enhanced::*;
|
||||||
pub use acc721::*;
|
pub use acc721::*;
|
||||||
|
|
@ -76,13 +88,13 @@ pub use acc_redemption::*;
|
||||||
pub use acc_insurance::*;
|
pub use acc_insurance::*;
|
||||||
pub use acc_governance::*;
|
pub use acc_governance::*;
|
||||||
pub use acc_xtzh::*;
|
pub use acc_xtzh::*;
|
||||||
// 新增协议导出(具体类型,避免 Result 类型冲突)
|
|
||||||
// ACC-1410: 分区代币协议
|
// ACC-1410: 分区代币协议
|
||||||
pub use acc1410::{
|
pub use acc1410::{
|
||||||
Acc1410, Acc1410Error, ExtendedGNACS, GNACSExtension, PartitionInfo, PartitionType,
|
Acc1410, Acc1410Error, ExtendedGNACS, GNACSExtension, PartitionInfo, PartitionType,
|
||||||
OperatorManager, TransferManager, PartitionManager,
|
OperatorManager, TransferManager, PartitionManager,
|
||||||
};
|
};
|
||||||
// ACC-1400: 证券代币协议(继承自 acc1410,直接导出 Acc1400 结构体)
|
// ACC-1400: 证券代币协议
|
||||||
pub use acc1400::Acc1400;
|
pub use acc1400::Acc1400;
|
||||||
// ACC-1594: 收益分配协议
|
// ACC-1594: 收益分配协议
|
||||||
pub use acc1594::{
|
pub use acc1594::{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue