504 lines
16 KiB
Rust
504 lines
16 KiB
Rust
// Collateral Lending System
|
||
// 资产抵押借贷系统 - RWA资产抵押借XTZH
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 贷款状态
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum LoanStatus {
|
||
Active, // 活跃
|
||
Repaid, // 已还款
|
||
Liquidated, // 已清算
|
||
Defaulted, // 违约
|
||
}
|
||
|
||
/// 贷款记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct Loan {
|
||
/// 贷款ID
|
||
pub loan_id: String,
|
||
/// 借款人
|
||
pub borrower: String,
|
||
/// 抵押资产TOKEN ID
|
||
pub collateral_asset_id: String,
|
||
/// 抵押份额数量
|
||
pub collateral_amount: u128,
|
||
/// 抵押资产估值(XTZH)
|
||
pub collateral_value: u128,
|
||
/// 借款金额(XTZH)
|
||
pub loan_amount: u128,
|
||
/// 年利率(基点,如500=5%)
|
||
pub interest_rate: u16,
|
||
/// 抵押率(基点,如15000=150%)
|
||
pub ltv_ratio: u16,
|
||
/// 创建时间
|
||
pub created_at: u64,
|
||
/// 到期时间
|
||
pub due_date: u64,
|
||
/// 已还本金
|
||
pub repaid_principal: u128,
|
||
/// 已还利息
|
||
pub repaid_interest: u128,
|
||
/// 状态
|
||
pub status: LoanStatus,
|
||
}
|
||
|
||
impl Loan {
|
||
/// 计算应付利息
|
||
pub fn calculate_interest(&self, current_time: u64) -> u128 {
|
||
if current_time <= self.created_at {
|
||
return 0;
|
||
}
|
||
|
||
let time_elapsed = current_time - self.created_at;
|
||
let years = time_elapsed as u128 * 1_000_000_000_000_000_000 / (365 * 24 * 3600);
|
||
|
||
let outstanding = self.loan_amount - self.repaid_principal;
|
||
(outstanding * self.interest_rate as u128 * years) / (10000 * 1_000_000_000_000_000_000)
|
||
}
|
||
|
||
/// 计算总应还金额
|
||
pub fn calculate_total_due(&self, current_time: u64) -> u128 {
|
||
let interest = self.calculate_interest(current_time);
|
||
let outstanding_principal = self.loan_amount - self.repaid_principal;
|
||
outstanding_principal + interest - self.repaid_interest
|
||
}
|
||
|
||
/// 计算当前健康度(抵押价值/贷款价值)
|
||
pub fn calculate_health_factor(&self, current_collateral_value: u128) -> u128 {
|
||
if self.loan_amount == 0 {
|
||
return u128::MAX;
|
||
}
|
||
(current_collateral_value * 10000) / self.loan_amount
|
||
}
|
||
|
||
/// 是否需要清算
|
||
pub fn needs_liquidation(&self, current_collateral_value: u128, liquidation_threshold: u16) -> bool {
|
||
let health_factor = self.calculate_health_factor(current_collateral_value);
|
||
health_factor < liquidation_threshold as u128
|
||
}
|
||
}
|
||
|
||
/// 抵押借贷系统
|
||
pub struct CollateralLendingSystem {
|
||
/// 贷款记录 (loan_id -> Loan)
|
||
loans: HashMap<String, Loan>,
|
||
/// 用户XTZH余额
|
||
xtzh_balances: HashMap<String, u128>,
|
||
/// 用户资产份额 (user, asset_id) -> shares
|
||
asset_shares: HashMap<(String, String), u128>,
|
||
/// 资产估值 (asset_id -> value in XTZH)
|
||
asset_valuations: HashMap<String, u128>,
|
||
/// 系统参数
|
||
params: LendingParams,
|
||
/// 统计信息
|
||
stats: LendingStats,
|
||
}
|
||
|
||
/// 借贷参数
|
||
#[derive(Debug, Clone)]
|
||
pub struct LendingParams {
|
||
/// 最大抵押率(基点,如7500=75%)
|
||
pub max_ltv: u16,
|
||
/// 清算阈值(基点,如12000=120%)
|
||
pub liquidation_threshold: u16,
|
||
/// 清算奖励(基点,如500=5%)
|
||
pub liquidation_bonus: u16,
|
||
/// 默认年利率(基点)
|
||
pub default_interest_rate: u16,
|
||
}
|
||
|
||
impl Default for LendingParams {
|
||
fn default() -> Self {
|
||
Self {
|
||
max_ltv: 7500, // 75%
|
||
liquidation_threshold: 12000, // 120%
|
||
liquidation_bonus: 500, // 5%
|
||
default_interest_rate: 800, // 8%
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 借贷统计
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct LendingStats {
|
||
/// 总贷款数
|
||
pub total_loans: u64,
|
||
/// 活跃贷款数
|
||
pub active_loans: u64,
|
||
/// 总借出金额
|
||
pub total_lent: u128,
|
||
/// 总抵押价值
|
||
pub total_collateral_value: u128,
|
||
/// 总清算次数
|
||
pub total_liquidations: u64,
|
||
}
|
||
|
||
impl CollateralLendingSystem {
|
||
/// 创建新系统
|
||
pub fn new(params: LendingParams) -> Self {
|
||
Self {
|
||
loans: HashMap::new(),
|
||
xtzh_balances: HashMap::new(),
|
||
asset_shares: HashMap::new(),
|
||
asset_valuations: HashMap::new(),
|
||
params,
|
||
stats: LendingStats::default(),
|
||
}
|
||
}
|
||
|
||
/// 设置XTZH余额
|
||
pub fn set_xtzh_balance(&mut self, user: String, amount: u128) {
|
||
self.xtzh_balances.insert(user, amount);
|
||
}
|
||
|
||
/// 获取XTZH余额
|
||
pub fn get_xtzh_balance(&self, user: &str) -> u128 {
|
||
*self.xtzh_balances.get(user).unwrap_or(&0)
|
||
}
|
||
|
||
/// 设置资产份额
|
||
pub fn set_asset_shares(&mut self, user: String, asset_id: String, shares: u128) {
|
||
self.asset_shares.insert((user, asset_id), shares);
|
||
}
|
||
|
||
/// 获取资产份额
|
||
pub fn get_asset_shares(&self, user: &str, asset_id: &str) -> u128 {
|
||
*self.asset_shares.get(&(user.to_string(), asset_id.to_string())).unwrap_or(&0)
|
||
}
|
||
|
||
/// 设置资产估值
|
||
pub fn set_asset_valuation(&mut self, asset_id: String, value: u128) {
|
||
self.asset_valuations.insert(asset_id, value);
|
||
}
|
||
|
||
/// 获取资产估值
|
||
pub fn get_asset_valuation(&self, asset_id: &str) -> u128 {
|
||
*self.asset_valuations.get(asset_id).unwrap_or(&0)
|
||
}
|
||
|
||
/// 创建贷款
|
||
pub fn create_loan(
|
||
&mut self,
|
||
borrower: String,
|
||
collateral_asset_id: String,
|
||
collateral_amount: u128,
|
||
loan_amount: u128,
|
||
duration_days: u64,
|
||
) -> Result<String, String> {
|
||
// 验证抵押品
|
||
let asset_shares = self.get_asset_shares(&borrower, &collateral_asset_id);
|
||
if asset_shares < collateral_amount {
|
||
return Err(format!("Insufficient collateral: {} < {}", asset_shares, collateral_amount));
|
||
}
|
||
|
||
// 获取抵押品估值
|
||
let unit_value = self.get_asset_valuation(&collateral_asset_id);
|
||
if unit_value == 0 {
|
||
return Err("Asset valuation not available".to_string());
|
||
}
|
||
|
||
let collateral_value = unit_value * collateral_amount;
|
||
|
||
// 验证抵押率
|
||
let ltv = (loan_amount * 10000) / collateral_value;
|
||
if ltv > self.params.max_ltv as u128 {
|
||
return Err(format!("LTV too high: {}% > {}%", ltv / 100, self.params.max_ltv / 100));
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.expect("mainnet: handle error")
|
||
.as_secs();
|
||
|
||
let loan_id = format!("loan_{}_{}", borrower, now);
|
||
let due_date = now + duration_days * 24 * 3600;
|
||
|
||
// 锁定抵押品
|
||
self.set_asset_shares(
|
||
borrower.clone(),
|
||
collateral_asset_id.clone(),
|
||
asset_shares - collateral_amount,
|
||
);
|
||
|
||
// 发放贷款
|
||
let xtzh_balance = self.get_xtzh_balance(&borrower);
|
||
self.set_xtzh_balance(borrower.clone(), xtzh_balance + loan_amount);
|
||
|
||
// 创建贷款记录
|
||
let loan = Loan {
|
||
loan_id: loan_id.clone(),
|
||
borrower,
|
||
collateral_asset_id,
|
||
collateral_amount,
|
||
collateral_value,
|
||
loan_amount,
|
||
interest_rate: self.params.default_interest_rate,
|
||
ltv_ratio: ltv as u16,
|
||
created_at: now,
|
||
due_date,
|
||
repaid_principal: 0,
|
||
repaid_interest: 0,
|
||
status: LoanStatus::Active,
|
||
};
|
||
|
||
self.loans.insert(loan_id.clone(), loan);
|
||
self.stats.total_loans += 1;
|
||
self.stats.active_loans += 1;
|
||
self.stats.total_lent += loan_amount;
|
||
self.stats.total_collateral_value += collateral_value;
|
||
|
||
Ok(loan_id)
|
||
}
|
||
|
||
/// 还款
|
||
pub fn repay_loan(
|
||
&mut self,
|
||
loan_id: &str,
|
||
amount: u128,
|
||
) -> Result<(), String> {
|
||
// 先收集需要的数据
|
||
let (borrower, collateral_asset_id, collateral_amount) = {
|
||
let loan = self.loans.get(loan_id).ok_or("Loan not found")?;
|
||
|
||
if loan.status != LoanStatus::Active {
|
||
return Err("Loan is not active".to_string());
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.expect("mainnet: handle error")
|
||
.as_secs();
|
||
|
||
let total_due = loan.calculate_total_due(now);
|
||
if amount > total_due {
|
||
return Err(format!("Amount exceeds total due: {} > {}", amount, total_due));
|
||
}
|
||
|
||
// 验证余额
|
||
let xtzh_balance = self.get_xtzh_balance(&loan.borrower);
|
||
if xtzh_balance < amount {
|
||
return Err(format!("Insufficient XTZH: {} < {}", xtzh_balance, amount));
|
||
}
|
||
|
||
(loan.borrower.clone(), loan.collateral_asset_id.clone(), loan.collateral_amount)
|
||
};
|
||
|
||
// 扣除还款
|
||
let xtzh_balance = self.get_xtzh_balance(&borrower);
|
||
self.set_xtzh_balance(borrower.clone(), xtzh_balance - amount);
|
||
|
||
// 更新贷款记录
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.expect("mainnet: handle error")
|
||
.as_secs();
|
||
let loan = self.loans.get_mut(loan_id).expect("mainnet: handle error");
|
||
let interest_due = loan.calculate_interest(now) - loan.repaid_interest;
|
||
|
||
if amount >= interest_due {
|
||
// 先还利息,再还本金
|
||
loan.repaid_interest += interest_due;
|
||
loan.repaid_principal += amount - interest_due;
|
||
} else {
|
||
// 只还利息
|
||
loan.repaid_interest += amount;
|
||
}
|
||
|
||
// 检查是否全部还清
|
||
if loan.repaid_principal >= loan.loan_amount {
|
||
loan.status = LoanStatus::Repaid;
|
||
self.stats.active_loans -= 1;
|
||
|
||
// 归还抵押品
|
||
let asset_shares = self.get_asset_shares(&borrower, &collateral_asset_id);
|
||
self.set_asset_shares(
|
||
borrower,
|
||
collateral_asset_id,
|
||
asset_shares + collateral_amount,
|
||
);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 清算贷款
|
||
pub fn liquidate_loan(
|
||
&mut self,
|
||
loan_id: &str,
|
||
liquidator: String,
|
||
) -> Result<u128, String> {
|
||
// 先收集所有需要的数据
|
||
let (collateral_asset_id, collateral_amount, liquidation_amount) = {
|
||
let loan = self.loans.get(loan_id).ok_or("Loan not found")?;
|
||
|
||
if loan.status != LoanStatus::Active {
|
||
return Err("Loan is not active".to_string());
|
||
}
|
||
|
||
// 获取当前抵押品价值
|
||
let unit_value = self.get_asset_valuation(&loan.collateral_asset_id);
|
||
let current_collateral_value = unit_value * loan.collateral_amount;
|
||
|
||
// 检查是否需要清算
|
||
if !loan.needs_liquidation(current_collateral_value, self.params.liquidation_threshold) {
|
||
return Err("Loan is healthy, cannot liquidate".to_string());
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.expect("mainnet: handle error")
|
||
.as_secs();
|
||
|
||
let total_due = loan.calculate_total_due(now);
|
||
|
||
// 计算清算金额(含奖励)
|
||
let liquidation_amount = total_due + (total_due * self.params.liquidation_bonus as u128 / 10000);
|
||
|
||
(loan.collateral_asset_id.clone(), loan.collateral_amount, liquidation_amount)
|
||
};
|
||
|
||
// 验证清算人余额
|
||
let liquidator_balance = self.get_xtzh_balance(&liquidator);
|
||
if liquidator_balance < liquidation_amount {
|
||
return Err(format!("Insufficient XTZH for liquidation: {} < {}", liquidator_balance, liquidation_amount));
|
||
}
|
||
|
||
// 扣除清算人XTZH
|
||
self.set_xtzh_balance(liquidator.clone(), liquidator_balance - liquidation_amount);
|
||
|
||
// 转移抵押品给清算人
|
||
let liquidator_shares = self.get_asset_shares(&liquidator, &collateral_asset_id);
|
||
self.set_asset_shares(
|
||
liquidator,
|
||
collateral_asset_id,
|
||
liquidator_shares + collateral_amount,
|
||
);
|
||
|
||
// 更新贷款状态
|
||
let loan = self.loans.get_mut(loan_id).expect("mainnet: handle error");
|
||
loan.status = LoanStatus::Liquidated;
|
||
|
||
self.stats.active_loans -= 1;
|
||
self.stats.total_liquidations += 1;
|
||
|
||
Ok(liquidation_amount)
|
||
}
|
||
|
||
/// 获取贷款
|
||
pub fn get_loan(&self, loan_id: &str) -> Option<&Loan> {
|
||
self.loans.get(loan_id)
|
||
}
|
||
|
||
/// 获取统计信息
|
||
pub fn get_stats(&self) -> &LendingStats {
|
||
&self.stats
|
||
}
|
||
|
||
/// 获取参数
|
||
pub fn get_params(&self) -> &LendingParams {
|
||
&self.params
|
||
}
|
||
}
|
||
|
||
impl Default for CollateralLendingSystem {
|
||
fn default() -> Self {
|
||
Self::new(LendingParams::default())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_create_loan() {
|
||
let mut system = CollateralLendingSystem::default();
|
||
|
||
system.set_asset_shares("borrower".to_string(), "asset1".to_string(), 100);
|
||
system.set_asset_valuation("asset1".to_string(), 1_000_000_000_000_000_000_000);
|
||
|
||
let loan_id = system.create_loan(
|
||
"borrower".to_string(),
|
||
"asset1".to_string(),
|
||
10,
|
||
7_000_000_000_000_000_000_000,
|
||
365,
|
||
).expect("mainnet: handle error");
|
||
|
||
assert!(system.get_loan(&loan_id).is_some());
|
||
assert_eq!(system.stats.total_loans, 1);
|
||
assert_eq!(system.stats.active_loans, 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_repay_loan() {
|
||
let mut system = CollateralLendingSystem::default();
|
||
|
||
system.set_asset_shares("borrower".to_string(), "asset1".to_string(), 100);
|
||
system.set_asset_valuation("asset1".to_string(), 1_000_000_000_000_000_000_000);
|
||
|
||
let loan_id = system.create_loan(
|
||
"borrower".to_string(),
|
||
"asset1".to_string(),
|
||
10,
|
||
7_000_000_000_000_000_000_000,
|
||
365,
|
||
).expect("mainnet: handle error");
|
||
|
||
let loan_amount = system.get_loan(&loan_id).expect("mainnet: handle error").loan_amount;
|
||
system.repay_loan(&loan_id, loan_amount).expect("mainnet: handle error");
|
||
|
||
let loan = system.get_loan(&loan_id).expect("mainnet: handle error");
|
||
assert_eq!(loan.status, LoanStatus::Repaid);
|
||
}
|
||
|
||
#[test]
|
||
fn test_liquidation() {
|
||
let mut system = CollateralLendingSystem::default();
|
||
|
||
system.set_asset_shares("borrower".to_string(), "asset1".to_string(), 100);
|
||
system.set_asset_valuation("asset1".to_string(), 1_000_000_000_000_000_000_000);
|
||
|
||
let loan_id = system.create_loan(
|
||
"borrower".to_string(),
|
||
"asset1".to_string(),
|
||
10,
|
||
7_000_000_000_000_000_000_000,
|
||
365,
|
||
).expect("mainnet: handle error");
|
||
|
||
// 抵押品价值下跌
|
||
system.set_asset_valuation("asset1".to_string(), 500_000_000_000_000_000_000);
|
||
|
||
system.set_xtzh_balance("liquidator".to_string(), 10_000_000_000_000_000_000_000);
|
||
|
||
let result = system.liquidate_loan(&loan_id, "liquidator".to_string());
|
||
assert!(result.is_ok());
|
||
assert_eq!(system.stats.total_liquidations, 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_health_factor() {
|
||
let mut system = CollateralLendingSystem::default();
|
||
|
||
system.set_asset_shares("borrower".to_string(), "asset1".to_string(), 100);
|
||
system.set_asset_valuation("asset1".to_string(), 1_000_000_000_000_000_000_000);
|
||
|
||
let loan_id = system.create_loan(
|
||
"borrower".to_string(),
|
||
"asset1".to_string(),
|
||
10,
|
||
7_000_000_000_000_000_000_000,
|
||
365,
|
||
).expect("mainnet: handle error");
|
||
|
||
let loan = system.get_loan(&loan_id).expect("mainnet: handle error");
|
||
let health_factor = loan.calculate_health_factor(10_000_000_000_000_000_000_000);
|
||
|
||
// 健康度应该是 10000/7000 * 10000 = 14285
|
||
assert!(health_factor > 14000 && health_factor < 15000);
|
||
}
|
||
}
|