527 lines
16 KiB
Rust
527 lines
16 KiB
Rust
// ACC-XTZH: XTZH原生代币协议
|
||
// XTZH是NAC公链的原生资产稳定币,具有双重价值锚定:
|
||
// 1. 一级锚定:RWA资产或合格跨链资产的价值
|
||
// 2. 二级锚定:1.25倍黄金永续合约储备
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// XTZH代币信息
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct XTZHToken {
|
||
/// 代币名称
|
||
pub name: String,
|
||
/// 代币符号
|
||
pub symbol: String,
|
||
/// 小数位数
|
||
pub decimals: u8,
|
||
/// 总供应量
|
||
pub total_supply: u128,
|
||
/// 流通供应量
|
||
pub circulating_supply: u128,
|
||
/// 黄金储备覆盖率(GCR,百分比,100表示100%)
|
||
pub gold_coverage_ratio: u16,
|
||
/// 最低黄金覆盖率(宪法级参数,需90%超级多数修改)
|
||
pub min_gold_coverage_ratio: u16,
|
||
}
|
||
|
||
/// XTZH账户余额
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct XTZHBalance {
|
||
/// 可用余额
|
||
pub available: u128,
|
||
/// 锁定余额(用于铸造抵押)
|
||
pub locked: u128,
|
||
/// 冻结余额(用于清算)
|
||
pub frozen: u128,
|
||
}
|
||
|
||
/// XTZH转账记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct XTZHTransfer {
|
||
/// 转账ID
|
||
pub transfer_id: String,
|
||
/// 发送方地址
|
||
pub from: String,
|
||
/// 接收方地址
|
||
pub to: String,
|
||
/// 转账金额
|
||
pub amount: u128,
|
||
/// 时间戳
|
||
pub timestamp: u64,
|
||
/// 交易哈希
|
||
pub tx_hash: String,
|
||
}
|
||
|
||
/// XTZH铸造记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct XTZHMintRecord {
|
||
/// 铸造ID
|
||
pub mint_id: String,
|
||
/// 资产DNA哈希(RWA资产或跨链资产)
|
||
pub asset_dna_hash: String,
|
||
/// 资产类型(RWA或CrossChain)
|
||
pub asset_type: AssetType,
|
||
/// 铸造数量
|
||
pub amount: u128,
|
||
/// 所有者DID
|
||
pub owner_did: String,
|
||
/// 黄金储备锚定哈希
|
||
pub gold_reserve_anchor_hash: String,
|
||
/// 健康因子(HF,百分比,100表示100%)
|
||
pub health_factor: u16,
|
||
/// 铸造时间戳
|
||
pub timestamp: u64,
|
||
/// 状态
|
||
pub status: MintStatus,
|
||
}
|
||
|
||
/// 资产类型
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||
pub enum AssetType {
|
||
/// RWA资产
|
||
RWA,
|
||
/// 跨链资产
|
||
CrossChain,
|
||
}
|
||
|
||
/// 铸造状态
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||
pub enum MintStatus {
|
||
/// 活跃
|
||
Active,
|
||
/// 预警(HF < 1.1)
|
||
Warning,
|
||
/// 清算中(HF < 1.0)
|
||
Liquidating,
|
||
/// 已赎回
|
||
Redeemed,
|
||
}
|
||
|
||
/// XTZH协议管理器
|
||
#[derive(Debug, Clone)]
|
||
pub struct XTZHProtocol {
|
||
/// XTZH代币信息
|
||
pub token: XTZHToken,
|
||
/// 账户余额映射(地址 -> 余额)
|
||
pub balances: HashMap<String, XTZHBalance>,
|
||
/// 铸造记录映射(铸造ID -> 铸造记录)
|
||
pub mint_records: HashMap<String, XTZHMintRecord>,
|
||
/// 转账记录
|
||
pub transfers: Vec<XTZHTransfer>,
|
||
}
|
||
|
||
impl XTZHProtocol {
|
||
/// 创建新的XTZH协议实例
|
||
pub fn new() -> Self {
|
||
Self {
|
||
token: XTZHToken {
|
||
name: "XTZH".to_string(),
|
||
symbol: "XTZH".to_string(),
|
||
decimals: 8,
|
||
total_supply: 0,
|
||
circulating_supply: 0,
|
||
gold_coverage_ratio: 125, // 初始125%覆盖率
|
||
min_gold_coverage_ratio: 100, // 最低100%覆盖率
|
||
},
|
||
balances: HashMap::new(),
|
||
mint_records: HashMap::new(),
|
||
transfers: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 获取账户余额
|
||
pub fn balance_of(&self, address: &str) -> XTZHBalance {
|
||
self.balances
|
||
.get(address)
|
||
.cloned()
|
||
.unwrap_or(XTZHBalance {
|
||
available: 0,
|
||
locked: 0,
|
||
frozen: 0,
|
||
})
|
||
}
|
||
|
||
/// 获取账户总余额
|
||
pub fn total_balance_of(&self, address: &str) -> u128 {
|
||
let balance = self.balance_of(address);
|
||
balance.available + balance.locked + balance.frozen
|
||
}
|
||
|
||
/// 转账
|
||
pub fn transfer(
|
||
&mut self,
|
||
from: &str,
|
||
to: &str,
|
||
amount: u128,
|
||
timestamp: u64,
|
||
) -> Result<String, String> {
|
||
// 检查发送方余额
|
||
let from_balance = self.balance_of(from);
|
||
if from_balance.available < amount {
|
||
return Err("Insufficient balance".to_string());
|
||
}
|
||
|
||
// 扣除发送方余额
|
||
let mut new_from_balance = from_balance.clone();
|
||
new_from_balance.available -= amount;
|
||
self.balances.insert(from.to_string(), new_from_balance);
|
||
|
||
// 增加接收方余额
|
||
let to_balance = self.balance_of(to);
|
||
let mut new_to_balance = to_balance.clone();
|
||
new_to_balance.available += amount;
|
||
self.balances.insert(to.to_string(), new_to_balance);
|
||
|
||
// 生成转账ID和交易哈希
|
||
let transfer_id = format!("transfer_{}", timestamp);
|
||
let tx_hash = format!("0x{:x}", timestamp);
|
||
|
||
// 记录转账
|
||
let transfer = XTZHTransfer {
|
||
transfer_id: transfer_id.clone(),
|
||
from: from.to_string(),
|
||
to: to.to_string(),
|
||
amount,
|
||
timestamp,
|
||
tx_hash,
|
||
};
|
||
self.transfers.push(transfer);
|
||
|
||
Ok(transfer_id)
|
||
}
|
||
|
||
/// 铸造XTZH(内部方法,由铸造协议调用)
|
||
pub fn mint(
|
||
&mut self,
|
||
mint_id: &str,
|
||
asset_dna_hash: &str,
|
||
asset_type: AssetType,
|
||
amount: u128,
|
||
owner_did: &str,
|
||
gold_reserve_anchor_hash: &str,
|
||
timestamp: u64,
|
||
) -> Result<(), String> {
|
||
// 检查铸造ID是否已存在
|
||
if self.mint_records.contains_key(mint_id) {
|
||
return Err("Mint ID already exists".to_string());
|
||
}
|
||
|
||
// 创建铸造记录
|
||
let mint_record = XTZHMintRecord {
|
||
mint_id: mint_id.to_string(),
|
||
asset_dna_hash: asset_dna_hash.to_string(),
|
||
asset_type,
|
||
amount,
|
||
owner_did: owner_did.to_string(),
|
||
gold_reserve_anchor_hash: gold_reserve_anchor_hash.to_string(),
|
||
health_factor: 150, // 初始健康因子150%
|
||
timestamp,
|
||
status: MintStatus::Active,
|
||
};
|
||
|
||
// 增加所有者余额
|
||
let owner_balance = self.balance_of(owner_did);
|
||
let mut new_owner_balance = owner_balance.clone();
|
||
new_owner_balance.available += amount;
|
||
self.balances
|
||
.insert(owner_did.to_string(), new_owner_balance);
|
||
|
||
// 更新总供应量和流通供应量
|
||
self.token.total_supply += amount;
|
||
self.token.circulating_supply += amount;
|
||
|
||
// 记录铸造
|
||
self.mint_records
|
||
.insert(mint_id.to_string(), mint_record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 销毁XTZH(内部方法,由赎回/清算协议调用)
|
||
pub fn burn(&mut self, address: &str, amount: u128) -> Result<(), String> {
|
||
// 检查余额
|
||
let balance = self.balance_of(address);
|
||
if balance.available < amount {
|
||
return Err("Insufficient balance to burn".to_string());
|
||
}
|
||
|
||
// 扣除余额
|
||
let mut new_balance = balance.clone();
|
||
new_balance.available -= amount;
|
||
self.balances.insert(address.to_string(), new_balance);
|
||
|
||
// 更新总供应量和流通供应量
|
||
self.token.total_supply -= amount;
|
||
self.token.circulating_supply -= amount;
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 锁定XTZH(用于铸造抵押)
|
||
pub fn lock(&mut self, address: &str, amount: u128) -> Result<(), String> {
|
||
let balance = self.balance_of(address);
|
||
if balance.available < amount {
|
||
return Err("Insufficient available balance to lock".to_string());
|
||
}
|
||
|
||
let mut new_balance = balance.clone();
|
||
new_balance.available -= amount;
|
||
new_balance.locked += amount;
|
||
self.balances.insert(address.to_string(), new_balance);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 解锁XTZH
|
||
pub fn unlock(&mut self, address: &str, amount: u128) -> Result<(), String> {
|
||
let balance = self.balance_of(address);
|
||
if balance.locked < amount {
|
||
return Err("Insufficient locked balance to unlock".to_string());
|
||
}
|
||
|
||
let mut new_balance = balance.clone();
|
||
new_balance.locked -= amount;
|
||
new_balance.available += amount;
|
||
self.balances.insert(address.to_string(), new_balance);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 冻结XTZH(用于清算)
|
||
pub fn freeze(&mut self, address: &str, amount: u128) -> Result<(), String> {
|
||
let balance = self.balance_of(address);
|
||
if balance.available < amount {
|
||
return Err("Insufficient available balance to freeze".to_string());
|
||
}
|
||
|
||
let mut new_balance = balance.clone();
|
||
new_balance.available -= amount;
|
||
new_balance.frozen += amount;
|
||
self.balances.insert(address.to_string(), new_balance);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 解冻XTZH
|
||
pub fn unfreeze(&mut self, address: &str, amount: u128) -> Result<(), String> {
|
||
let balance = self.balance_of(address);
|
||
if balance.frozen < amount {
|
||
return Err("Insufficient frozen balance to unfreeze".to_string());
|
||
}
|
||
|
||
let mut new_balance = balance.clone();
|
||
new_balance.frozen -= amount;
|
||
new_balance.available += amount;
|
||
self.balances.insert(address.to_string(), new_balance);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 更新健康因子
|
||
pub fn update_health_factor(
|
||
&mut self,
|
||
mint_id: &str,
|
||
new_health_factor: u16,
|
||
) -> Result<(), String> {
|
||
let mint_record = self
|
||
.mint_records
|
||
.get_mut(mint_id)
|
||
.ok_or("Mint record not found")?;
|
||
|
||
mint_record.health_factor = new_health_factor;
|
||
|
||
// 更新状态
|
||
if new_health_factor < 100 {
|
||
mint_record.status = MintStatus::Liquidating;
|
||
} else if new_health_factor < 110 {
|
||
mint_record.status = MintStatus::Warning;
|
||
} else {
|
||
mint_record.status = MintStatus::Active;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 更新黄金储备覆盖率
|
||
pub fn update_gold_coverage_ratio(&mut self, new_gcr: u16) -> Result<(), String> {
|
||
if new_gcr < self.token.min_gold_coverage_ratio {
|
||
return Err(format!(
|
||
"GCR {} is below minimum {}",
|
||
new_gcr, self.token.min_gold_coverage_ratio
|
||
));
|
||
}
|
||
|
||
self.token.gold_coverage_ratio = new_gcr;
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取所有预警铸造记录
|
||
pub fn get_warning_mints(&self) -> Vec<&XTZHMintRecord> {
|
||
self.mint_records
|
||
.values()
|
||
.filter(|record| record.status == MintStatus::Warning)
|
||
.collect()
|
||
}
|
||
|
||
/// 获取所有清算中铸造记录
|
||
pub fn get_liquidating_mints(&self) -> Vec<&XTZHMintRecord> {
|
||
self.mint_records
|
||
.values()
|
||
.filter(|record| record.status == MintStatus::Liquidating)
|
||
.collect()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_xtzh_protocol_creation() {
|
||
let protocol = XTZHProtocol::new();
|
||
assert_eq!(protocol.token.symbol, "XTZH");
|
||
assert_eq!(protocol.token.decimals, 8);
|
||
assert_eq!(protocol.token.total_supply, 0);
|
||
assert_eq!(protocol.token.gold_coverage_ratio, 125);
|
||
}
|
||
|
||
#[test]
|
||
fn test_xtzh_mint() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
let result = protocol.mint(
|
||
"mint_001",
|
||
"asset_dna_hash_001",
|
||
AssetType::RWA,
|
||
1000_0000_0000, // 1000 XTZH
|
||
"owner_did_001",
|
||
"gold_reserve_anchor_001",
|
||
1700000000,
|
||
);
|
||
assert!(result.is_ok());
|
||
assert_eq!(protocol.token.total_supply, 1000_0000_0000);
|
||
assert_eq!(protocol.balance_of("owner_did_001").available, 1000_0000_0000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_xtzh_transfer() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
|
||
// 先铸造一些XTZH
|
||
protocol
|
||
.mint(
|
||
"mint_001",
|
||
"asset_dna_hash_001",
|
||
AssetType::RWA,
|
||
1000_0000_0000,
|
||
"alice",
|
||
"gold_reserve_anchor_001",
|
||
1700000000,
|
||
)
|
||
.expect("mainnet: handle error");
|
||
|
||
// 转账
|
||
let result = protocol.transfer("alice", "bob", 300_0000_0000, 1700000001);
|
||
assert!(result.is_ok());
|
||
assert_eq!(protocol.balance_of("alice").available, 700_0000_0000);
|
||
assert_eq!(protocol.balance_of("bob").available, 300_0000_0000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_xtzh_burn() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
|
||
// 先铸造
|
||
protocol
|
||
.mint(
|
||
"mint_001",
|
||
"asset_dna_hash_001",
|
||
AssetType::RWA,
|
||
1000_0000_0000,
|
||
"alice",
|
||
"gold_reserve_anchor_001",
|
||
1700000000,
|
||
)
|
||
.expect("mainnet: handle error");
|
||
|
||
// 销毁
|
||
let result = protocol.burn("alice", 300_0000_0000);
|
||
assert!(result.is_ok());
|
||
assert_eq!(protocol.balance_of("alice").available, 700_0000_0000);
|
||
assert_eq!(protocol.token.total_supply, 700_0000_0000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_xtzh_lock_unlock() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
|
||
// 先铸造
|
||
protocol
|
||
.mint(
|
||
"mint_001",
|
||
"asset_dna_hash_001",
|
||
AssetType::RWA,
|
||
1000_0000_0000,
|
||
"alice",
|
||
"gold_reserve_anchor_001",
|
||
1700000000,
|
||
)
|
||
.expect("mainnet: handle error");
|
||
|
||
// 锁定
|
||
protocol.lock("alice", 300_0000_0000).expect("mainnet: handle error");
|
||
let balance = protocol.balance_of("alice");
|
||
assert_eq!(balance.available, 700_0000_0000);
|
||
assert_eq!(balance.locked, 300_0000_0000);
|
||
|
||
// 解锁
|
||
protocol.unlock("alice", 100_0000_0000).expect("mainnet: handle error");
|
||
let balance = protocol.balance_of("alice");
|
||
assert_eq!(balance.available, 800_0000_0000);
|
||
assert_eq!(balance.locked, 200_0000_0000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_health_factor_update() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
|
||
// 先铸造
|
||
protocol
|
||
.mint(
|
||
"mint_001",
|
||
"asset_dna_hash_001",
|
||
AssetType::RWA,
|
||
1000_0000_0000,
|
||
"alice",
|
||
"gold_reserve_anchor_001",
|
||
1700000000,
|
||
)
|
||
.expect("mainnet: handle error");
|
||
|
||
// 更新健康因子到预警状态
|
||
protocol.update_health_factor("mint_001", 105).expect("mainnet: handle error");
|
||
let record = protocol.mint_records.get("mint_001").expect("mainnet: handle error");
|
||
assert_eq!(record.health_factor, 105);
|
||
assert_eq!(record.status, MintStatus::Warning);
|
||
|
||
// 更新健康因子到清算状态
|
||
protocol.update_health_factor("mint_001", 95).expect("mainnet: handle error");
|
||
let record = protocol.mint_records.get("mint_001").expect("mainnet: handle error");
|
||
assert_eq!(record.health_factor, 95);
|
||
assert_eq!(record.status, MintStatus::Liquidating);
|
||
}
|
||
|
||
#[test]
|
||
fn test_gold_coverage_ratio() {
|
||
let mut protocol = XTZHProtocol::new();
|
||
|
||
// 更新GCR
|
||
protocol.update_gold_coverage_ratio(110).expect("mainnet: handle error");
|
||
assert_eq!(protocol.token.gold_coverage_ratio, 110);
|
||
|
||
// 尝试设置低于最低值的GCR
|
||
let result = protocol.update_gold_coverage_ratio(95);
|
||
assert!(result.is_err());
|
||
}
|
||
}
|