173 lines
8.9 KiB
Rust
173 lines
8.9 KiB
Rust
//! 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<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) }
|
||
}
|