535 lines
16 KiB
Rust
535 lines
16 KiB
Rust
// ACC-Collateral: 资产抵押协议(Collateral Protocol)
|
||
//
|
||
// NAC原生的资产抵押标准,用于借贷和DeFi应用
|
||
// 100% NAC原生协议,不是任何现有标准的实现
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 抵押状态
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum CollateralStatus {
|
||
/// 活跃
|
||
Active,
|
||
/// 已清算
|
||
Liquidated,
|
||
/// 已释放
|
||
Released,
|
||
/// 已违约
|
||
Defaulted,
|
||
}
|
||
|
||
/// 抵押率类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum CollateralRatioType {
|
||
/// 初始抵押率(Initial LTV)
|
||
Initial,
|
||
/// 维持抵押率(Maintenance LTV)
|
||
Maintenance,
|
||
/// 清算抵押率(Liquidation LTV)
|
||
Liquidation,
|
||
}
|
||
|
||
/// 抵押记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct CollateralRecord {
|
||
/// 抵押ID
|
||
pub collateral_id: String,
|
||
/// 抵押资产ID
|
||
pub asset_id: String,
|
||
/// 抵押人地址
|
||
pub borrower: String,
|
||
/// 贷款人地址
|
||
pub lender: String,
|
||
/// 抵押资产价值(USD,以分为单位)
|
||
pub collateral_value: u128,
|
||
/// 借款金额(USD,以分为单位)
|
||
pub loan_amount: u128,
|
||
/// 初始抵押率(百分比,如150表示150%)
|
||
pub initial_ltv: u16,
|
||
/// 维持抵押率(百分比)
|
||
pub maintenance_ltv: u16,
|
||
/// 清算抵押率(百分比)
|
||
pub liquidation_ltv: u16,
|
||
/// 当前抵押率(百分比)
|
||
pub current_ltv: u16,
|
||
/// 利率(年化,基点,如500表示5%)
|
||
pub interest_rate: u16,
|
||
/// 抵押状态
|
||
pub status: CollateralStatus,
|
||
/// 创建时间
|
||
pub created_at: u64,
|
||
/// 到期时间
|
||
pub expires_at: u64,
|
||
/// 最后更新时间
|
||
pub updated_at: u64,
|
||
/// 清算时间
|
||
pub liquidated_at: Option<u64>,
|
||
}
|
||
|
||
/// 抵押错误类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum CollateralError {
|
||
/// 抵押记录不存在
|
||
RecordNotFound,
|
||
/// 抵押记录已存在
|
||
RecordAlreadyExists,
|
||
/// 资产不存在
|
||
AssetNotFound,
|
||
/// 抵押率不足
|
||
InsufficientCollateral,
|
||
/// 抵押已清算
|
||
AlreadyLiquidated,
|
||
/// 抵押已释放
|
||
AlreadyReleased,
|
||
/// 未到期
|
||
NotExpired,
|
||
/// 未授权操作
|
||
Unauthorized,
|
||
/// 无效的抵押率
|
||
InvalidLTV,
|
||
}
|
||
|
||
/// 抵押状态
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct CollateralState {
|
||
/// 抵押记录映射 (collateral_id -> CollateralRecord)
|
||
pub collaterals: HashMap<String, CollateralRecord>,
|
||
/// 资产抵押映射 (asset_id -> collateral_id)
|
||
pub asset_collaterals: HashMap<String, String>,
|
||
/// 借款人抵押列表 (borrower -> [collateral_ids])
|
||
pub borrower_collaterals: HashMap<String, Vec<String>>,
|
||
/// 贷款人抵押列表 (lender -> [collateral_ids])
|
||
pub lender_collaterals: HashMap<String, Vec<String>>,
|
||
}
|
||
|
||
/// ACC-Collateral接口
|
||
pub trait ACCCollateral {
|
||
/// 创建抵押
|
||
fn create_collateral(&mut self, record: CollateralRecord) -> Result<(), CollateralError>;
|
||
|
||
/// 获取抵押记录
|
||
fn get_collateral(&self, collateral_id: &str) -> Result<CollateralRecord, CollateralError>;
|
||
|
||
/// 获取资产的抵押记录
|
||
fn get_asset_collateral(&self, asset_id: &str) -> Result<CollateralRecord, CollateralError>;
|
||
|
||
/// 更新抵押资产价值
|
||
fn update_collateral_value(
|
||
&mut self,
|
||
collateral_id: &str,
|
||
new_value: u128,
|
||
) -> Result<(), CollateralError>;
|
||
|
||
/// 计算当前抵押率
|
||
fn calculate_ltv(&self, collateral_id: &str) -> Result<u16, CollateralError>;
|
||
|
||
/// 检查是否需要清算
|
||
fn is_liquidatable(&self, collateral_id: &str) -> bool;
|
||
|
||
/// 清算抵押
|
||
fn liquidate(&mut self, collateral_id: &str) -> Result<(), CollateralError>;
|
||
|
||
/// 释放抵押
|
||
fn release(&mut self, collateral_id: &str) -> Result<(), CollateralError>;
|
||
|
||
/// 部分还款
|
||
fn partial_repay(
|
||
&mut self,
|
||
collateral_id: &str,
|
||
amount: u128,
|
||
) -> Result<(), CollateralError>;
|
||
|
||
/// 获取借款人的抵押列表
|
||
fn get_borrower_collaterals(&self, borrower: &str) -> Vec<CollateralRecord>;
|
||
|
||
/// 获取贷款人的抵押列表
|
||
fn get_lender_collaterals(&self, lender: &str) -> Vec<CollateralRecord>;
|
||
|
||
/// 获取所有需要清算的抵押
|
||
fn get_liquidatable_collaterals(&self) -> Vec<CollateralRecord>;
|
||
}
|
||
|
||
/// ACC-Collateral标准实现
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct CollateralToken {
|
||
state: CollateralState,
|
||
}
|
||
|
||
impl CollateralToken {
|
||
/// 创建新的抵押管理器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
state: CollateralState {
|
||
collaterals: HashMap::new(),
|
||
asset_collaterals: HashMap::new(),
|
||
borrower_collaterals: HashMap::new(),
|
||
lender_collaterals: HashMap::new(),
|
||
},
|
||
}
|
||
}
|
||
|
||
/// 获取状态的可变引用
|
||
pub fn state_mut(&mut self) -> &mut CollateralState {
|
||
&mut self.state
|
||
}
|
||
|
||
/// 获取状态的不可变引用
|
||
pub fn state(&self) -> &CollateralState {
|
||
&self.state
|
||
}
|
||
|
||
/// 验证抵押率
|
||
fn validate_ltv(&self, record: &CollateralRecord) -> Result<(), CollateralError> {
|
||
// LTV应该是递增的:initial < maintenance < liquidation
|
||
if record.initial_ltv > record.maintenance_ltv
|
||
|| record.maintenance_ltv > record.liquidation_ltv
|
||
{
|
||
return Err(CollateralError::InvalidLTV);
|
||
}
|
||
|
||
// 检查初始抵押率是否满足要求
|
||
let ltv = (record.loan_amount * 10000 / record.collateral_value) as u16;
|
||
if ltv > record.initial_ltv {
|
||
return Err(CollateralError::InsufficientCollateral);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl Default for CollateralToken {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl ACCCollateral for CollateralToken {
|
||
fn create_collateral(&mut self, mut record: CollateralRecord) -> Result<(), CollateralError> {
|
||
// 检查抵押记录是否已存在
|
||
if self.state.collaterals.contains_key(&record.collateral_id) {
|
||
return Err(CollateralError::RecordAlreadyExists);
|
||
}
|
||
|
||
// 检查资产是否已被抵押
|
||
if self.state.asset_collaterals.contains_key(&record.asset_id) {
|
||
return Err(CollateralError::RecordAlreadyExists);
|
||
}
|
||
|
||
// 验证抵押率
|
||
self.validate_ltv(&record)?;
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
record.created_at = now;
|
||
record.updated_at = now;
|
||
record.status = CollateralStatus::Active;
|
||
record.current_ltv = (record.loan_amount * 10000 / record.collateral_value) as u16;
|
||
|
||
let collateral_id = record.collateral_id.clone();
|
||
let asset_id = record.asset_id.clone();
|
||
let borrower = record.borrower.clone();
|
||
let lender = record.lender.clone();
|
||
|
||
// 保存抵押记录
|
||
self.state
|
||
.collaterals
|
||
.insert(collateral_id.clone(), record);
|
||
|
||
// 更新资产抵押映射
|
||
self.state
|
||
.asset_collaterals
|
||
.insert(asset_id, collateral_id.clone());
|
||
|
||
// 更新借款人抵押列表
|
||
self.state
|
||
.borrower_collaterals
|
||
.entry(borrower)
|
||
.or_insert_with(Vec::new)
|
||
.push(collateral_id.clone());
|
||
|
||
// 更新贷款人抵押列表
|
||
self.state
|
||
.lender_collaterals
|
||
.entry(lender)
|
||
.or_insert_with(Vec::new)
|
||
.push(collateral_id);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_collateral(&self, collateral_id: &str) -> Result<CollateralRecord, CollateralError> {
|
||
self.state
|
||
.collaterals
|
||
.get(collateral_id)
|
||
.cloned()
|
||
.ok_or(CollateralError::RecordNotFound)
|
||
}
|
||
|
||
fn get_asset_collateral(&self, asset_id: &str) -> Result<CollateralRecord, CollateralError> {
|
||
let collateral_id = self
|
||
.state
|
||
.asset_collaterals
|
||
.get(asset_id)
|
||
.ok_or(CollateralError::AssetNotFound)?;
|
||
self.get_collateral(collateral_id)
|
||
}
|
||
|
||
fn update_collateral_value(
|
||
&mut self,
|
||
collateral_id: &str,
|
||
new_value: u128,
|
||
) -> Result<(), CollateralError> {
|
||
let mut record = self.get_collateral(collateral_id)?;
|
||
|
||
if record.status != CollateralStatus::Active {
|
||
return Err(CollateralError::AlreadyLiquidated);
|
||
}
|
||
|
||
record.collateral_value = new_value;
|
||
record.current_ltv = (record.loan_amount * 10000 / new_value) as u16;
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
record.updated_at = now;
|
||
|
||
self.state
|
||
.collaterals
|
||
.insert(collateral_id.to_string(), record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn calculate_ltv(&self, collateral_id: &str) -> Result<u16, CollateralError> {
|
||
let record = self.get_collateral(collateral_id)?;
|
||
Ok((record.loan_amount * 10000 / record.collateral_value) as u16)
|
||
}
|
||
|
||
fn is_liquidatable(&self, collateral_id: &str) -> bool {
|
||
if let Ok(record) = self.get_collateral(collateral_id) {
|
||
record.status == CollateralStatus::Active
|
||
&& record.current_ltv >= record.liquidation_ltv
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
fn liquidate(&mut self, collateral_id: &str) -> Result<(), CollateralError> {
|
||
let mut record = self.get_collateral(collateral_id)?;
|
||
|
||
if record.status != CollateralStatus::Active {
|
||
return Err(CollateralError::AlreadyLiquidated);
|
||
}
|
||
|
||
if !self.is_liquidatable(collateral_id) {
|
||
return Err(CollateralError::InsufficientCollateral);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
record.status = CollateralStatus::Liquidated;
|
||
record.liquidated_at = Some(now);
|
||
record.updated_at = now;
|
||
|
||
self.state
|
||
.collaterals
|
||
.insert(collateral_id.to_string(), record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn release(&mut self, collateral_id: &str) -> Result<(), CollateralError> {
|
||
let mut record = self.get_collateral(collateral_id)?;
|
||
|
||
if record.status != CollateralStatus::Active {
|
||
return Err(CollateralError::AlreadyReleased);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
record.status = CollateralStatus::Released;
|
||
record.updated_at = now;
|
||
|
||
self.state
|
||
.collaterals
|
||
.insert(collateral_id.to_string(), record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn partial_repay(
|
||
&mut self,
|
||
collateral_id: &str,
|
||
amount: u128,
|
||
) -> Result<(), CollateralError> {
|
||
let mut record = self.get_collateral(collateral_id)?;
|
||
|
||
if record.status != CollateralStatus::Active {
|
||
return Err(CollateralError::AlreadyLiquidated);
|
||
}
|
||
|
||
if amount > record.loan_amount {
|
||
return Err(CollateralError::Unauthorized);
|
||
}
|
||
|
||
record.loan_amount -= amount;
|
||
record.current_ltv = if record.loan_amount > 0 {
|
||
(record.loan_amount * 10000 / record.collateral_value) as u16
|
||
} else {
|
||
0
|
||
};
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
record.updated_at = now;
|
||
|
||
self.state
|
||
.collaterals
|
||
.insert(collateral_id.to_string(), record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_borrower_collaterals(&self, borrower: &str) -> Vec<CollateralRecord> {
|
||
self.state
|
||
.borrower_collaterals
|
||
.get(borrower)
|
||
.map(|collateral_ids| {
|
||
collateral_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.collaterals.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn get_lender_collaterals(&self, lender: &str) -> Vec<CollateralRecord> {
|
||
self.state
|
||
.lender_collaterals
|
||
.get(lender)
|
||
.map(|collateral_ids| {
|
||
collateral_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.collaterals.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn get_liquidatable_collaterals(&self) -> Vec<CollateralRecord> {
|
||
self.state
|
||
.collaterals
|
||
.values()
|
||
.filter(|record| {
|
||
record.status == CollateralStatus::Active
|
||
&& record.current_ltv >= record.liquidation_ltv
|
||
})
|
||
.cloned()
|
||
.collect()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_record() -> CollateralRecord {
|
||
CollateralRecord {
|
||
collateral_id: "COLL-001".to_string(),
|
||
asset_id: "RWA-001".to_string(),
|
||
borrower: "alice".to_string(),
|
||
lender: "bob".to_string(),
|
||
collateral_value: 1000000_00, // $10,000
|
||
loan_amount: 600000_00, // $6,000
|
||
initial_ltv: 7000, // 70%
|
||
maintenance_ltv: 8000, // 80%
|
||
liquidation_ltv: 8500, // 85%
|
||
current_ltv: 6000, // 60%
|
||
interest_rate: 500, // 5%
|
||
status: CollateralStatus::Active,
|
||
created_at: 0,
|
||
expires_at: 1234567890 + 31536000,
|
||
updated_at: 0,
|
||
liquidated_at: None,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_create_collateral() {
|
||
let mut collateral = CollateralToken::new();
|
||
let record = create_test_record();
|
||
assert!(collateral.create_collateral(record).is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_calculate_ltv() {
|
||
let mut collateral = CollateralToken::new();
|
||
let record = create_test_record();
|
||
collateral.create_collateral(record).unwrap();
|
||
|
||
let ltv = collateral.calculate_ltv("COLL-001").unwrap();
|
||
assert_eq!(ltv, 6000); // 60%
|
||
}
|
||
|
||
#[test]
|
||
fn test_update_collateral_value() {
|
||
let mut collateral = CollateralToken::new();
|
||
let record = create_test_record();
|
||
collateral.create_collateral(record).unwrap();
|
||
|
||
assert!(collateral
|
||
.update_collateral_value("COLL-001", 500000_00)
|
||
.is_ok());
|
||
let ltv = collateral.calculate_ltv("COLL-001").unwrap();
|
||
assert_eq!(ltv, 12000); // 120%
|
||
}
|
||
|
||
#[test]
|
||
fn test_liquidate() {
|
||
let mut collateral = CollateralToken::new();
|
||
let record = create_test_record();
|
||
// 创建正常的抵押记录(LTV = 60%)
|
||
collateral.create_collateral(record).unwrap();
|
||
|
||
// 通过降低抵押品价值使LTV上升到清算阈值以上
|
||
// 原始: loan_amount=600000_00, collateral_value=1000000_00, LTV=60%
|
||
// 要达到90% LTV,需要: collateral_value = 600000_00 * 10000 / 9000 = 666666_67
|
||
assert!(collateral
|
||
.update_collateral_value("COLL-001", 666666_67)
|
||
.is_ok());
|
||
|
||
// 现在LTV应该是90%,超过liquidation_ltv (85%)
|
||
assert!(collateral.liquidate("COLL-001").is_ok());
|
||
assert_eq!(
|
||
collateral.get_collateral("COLL-001").unwrap().status,
|
||
CollateralStatus::Liquidated
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_partial_repay() {
|
||
let mut collateral = CollateralToken::new();
|
||
let record = create_test_record();
|
||
collateral.create_collateral(record).unwrap();
|
||
|
||
assert!(collateral.partial_repay("COLL-001", 100000_00).is_ok());
|
||
assert_eq!(
|
||
collateral.get_collateral("COLL-001").unwrap().loan_amount,
|
||
500000_00
|
||
);
|
||
}
|
||
}
|