//! acc_xtzh - NAC 原生协议实现
//! 从 acc_remaining_protocols.rs 提取
use crate::primitives::{Address, Hash, Timestamp};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ACCXTZHError {
InsufficientBalance { holder: Address, available: u128, requested: u128 },
InsufficientReserve { required: u128, available: u128 },
InvalidConstitutionalReceipt,
Unauthorized(Address),
SDRPegViolation { current_rate: u128, min_rate: u128, max_rate: u128 },
GoldReserveInsufficient { required_ratio: u8, actual_ratio: u8 },
}
impl std::fmt::Display for ACCXTZHError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InsufficientBalance { holder, available, requested } => write!(f, "余额不足 {}: 可用 {},请求 {}", holder.to_hex(), available, requested),
Self::InsufficientReserve { required, available } => write!(f, "储备不足: 需要 {},可用 {}", required, available),
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),
Self::GoldReserveInsufficient { required_ratio, actual_ratio } => write!(f, "黄金储备不足: 要求 {}%,实际 {}%", required_ratio, actual_ratio),
}
}
}
#[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
,
/// 储备资产
pub reserve_assets: Vec,
/// 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,
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 { std::mem::take(&mut self.pending_events) }
}