use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum SyntheticAssetType {
Fiat,
Commodity,
Stock,
Index,
Crypto,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyntheticAsset {
pub asset_id: Uuid,
pub symbol: String,
pub name: String,
pub asset_type: SyntheticAssetType,
pub collateral_ratio: Decimal,
pub liquidation_ratio: Decimal,
pub mint_fee: Decimal,
pub burn_fee: Decimal,
pub total_supply: Decimal,
pub created_at: i64,
}
impl SyntheticAsset {
pub fn new(
symbol: String,
name: String,
asset_type: SyntheticAssetType,
collateral_ratio: Decimal,
liquidation_ratio: Decimal,
mint_fee: Decimal,
burn_fee: Decimal,
) -> Result<Self, &'static str> {
if collateral_ratio < dec!(1) {
return Err("Collateral ratio must be at least 1.0");
}
if liquidation_ratio >= collateral_ratio {
return Err("Liquidation ratio must be less than collateral ratio");
}
if mint_fee < Decimal::ZERO || mint_fee > dec!(0.1) {
return Err("Mint fee must be between 0 and 10%");
}
if burn_fee < Decimal::ZERO || burn_fee > dec!(0.1) {
return Err("Burn fee must be between 0 and 10%");
}
Ok(Self {
asset_id: Uuid::new_v4(),
symbol,
name,
asset_type,
collateral_ratio,
liquidation_ratio,
mint_fee,
burn_fee,
total_supply: Decimal::ZERO,
created_at: chrono::Utc::now().timestamp(),
})
}
pub fn required_collateral(&self, amount: Decimal, price: Decimal) -> Decimal {
amount * price * self.collateral_ratio
}
pub fn calculate_ratio(&self, collateral: Decimal, debt: Decimal, price: Decimal) -> Decimal {
if debt == Decimal::ZERO {
return Decimal::MAX;
}
collateral / (debt * price)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollateralDeposit {
pub deposit_id: Uuid,
pub user_id: Uuid,
pub collateral_asset_id: Uuid,
pub amount: Decimal,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyntheticPosition {
pub position_id: Uuid,
pub user_id: Uuid,
pub synthetic_asset_id: Uuid,
pub collateral_amount: Decimal,
pub collateral_asset_id: Uuid,
pub minted_amount: Decimal,
pub created_at: i64,
pub last_update: i64,
}
impl SyntheticPosition {
pub fn new(user_id: Uuid, synthetic_asset_id: Uuid, collateral_asset_id: Uuid) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
position_id: Uuid::new_v4(),
user_id,
synthetic_asset_id,
collateral_amount: Decimal::ZERO,
collateral_asset_id,
minted_amount: Decimal::ZERO,
created_at: now,
last_update: now,
}
}
pub fn collateral_ratio(&self, collateral_price: Decimal, synthetic_price: Decimal) -> Decimal {
if self.minted_amount == Decimal::ZERO {
return Decimal::MAX;
}
let collateral_value = self.collateral_amount * collateral_price;
let debt_value = self.minted_amount * synthetic_price;
collateral_value / debt_value
}
pub fn is_safe(
&self,
collateral_price: Decimal,
synthetic_price: Decimal,
liquidation_ratio: Decimal,
) -> bool {
self.collateral_ratio(collateral_price, synthetic_price) >= liquidation_ratio
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyntheticLiquidation {
pub liquidation_id: Uuid,
pub position_id: Uuid,
pub liquidator_id: Uuid,
pub debt_covered: Decimal,
pub collateral_seized: Decimal,
pub liquidation_penalty: Decimal,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceFeed {
pub asset_id: Uuid,
pub price: Decimal,
pub timestamp: i64,
pub confidence: Decimal,
}
pub struct SyntheticAssetManager {
assets: Arc<RwLock<HashMap<Uuid, SyntheticAsset>>>,
positions: Arc<RwLock<HashMap<Uuid, SyntheticPosition>>>,
price_feeds: Arc<RwLock<HashMap<Uuid, PriceFeed>>>,
user_positions: Arc<RwLock<HashMap<Uuid, Vec<Uuid>>>>,
}
impl SyntheticAssetManager {
pub fn new() -> Self {
Self {
assets: Arc::new(RwLock::new(HashMap::new())),
positions: Arc::new(RwLock::new(HashMap::new())),
price_feeds: Arc::new(RwLock::new(HashMap::new())),
user_positions: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn create_asset(&self, asset: SyntheticAsset) -> Result<Uuid, &'static str> {
let asset_id = asset.asset_id;
self.assets.write().await.insert(asset_id, asset);
Ok(asset_id)
}
pub async fn update_price(&self, feed: PriceFeed) {
self.price_feeds.write().await.insert(feed.asset_id, feed);
}
pub async fn get_price(&self, asset_id: Uuid) -> Option<Decimal> {
self.price_feeds
.read()
.await
.get(&asset_id)
.map(|feed| feed.price)
}
pub async fn mint(
&self,
user_id: Uuid,
synthetic_asset_id: Uuid,
collateral_asset_id: Uuid,
collateral_amount: Decimal,
mint_amount: Decimal,
) -> Result<Uuid, &'static str> {
if collateral_amount <= Decimal::ZERO {
return Err("Collateral amount must be positive");
}
if mint_amount <= Decimal::ZERO {
return Err("Mint amount must be positive");
}
let assets = self.assets.read().await;
let asset = assets
.get(&synthetic_asset_id)
.ok_or("Synthetic asset not found")?;
let required_ratio = asset.collateral_ratio;
let mint_fee = asset.mint_fee;
drop(assets);
let collateral_price = self
.get_price(collateral_asset_id)
.await
.ok_or("Collateral price not available")?;
let synthetic_price = self
.get_price(synthetic_asset_id)
.await
.ok_or("Synthetic price not available")?;
let collateral_value = collateral_amount * collateral_price;
let mint_value = mint_amount * synthetic_price;
let ratio = collateral_value / mint_value;
if ratio < required_ratio {
return Err("Insufficient collateralization");
}
let mut position = SyntheticPosition::new(user_id, synthetic_asset_id, collateral_asset_id);
position.collateral_amount = collateral_amount;
position.minted_amount = mint_amount;
let position_id = position.position_id;
self.positions.write().await.insert(position_id, position);
self.user_positions
.write()
.await
.entry(user_id)
.or_insert_with(Vec::new)
.push(position_id);
let net_mint = mint_amount * (dec!(1) - mint_fee);
self.assets
.write()
.await
.get_mut(&synthetic_asset_id)
.unwrap()
.total_supply += net_mint;
Ok(position_id)
}
pub async fn burn(
&self,
position_id: Uuid,
burn_amount: Decimal,
) -> Result<Decimal, &'static str> {
if burn_amount <= Decimal::ZERO {
return Err("Burn amount must be positive");
}
let mut positions = self.positions.write().await;
let position = positions
.get_mut(&position_id)
.ok_or("Position not found")?;
if burn_amount > position.minted_amount {
return Err("Burn amount exceeds debt");
}
let synthetic_asset_id = position.synthetic_asset_id;
position.minted_amount -= burn_amount;
position.last_update = chrono::Utc::now().timestamp();
drop(positions);
let assets = self.assets.read().await;
let asset = assets.get(&synthetic_asset_id).unwrap();
let burn_fee = asset.burn_fee;
drop(assets);
let fee_amount = burn_amount * burn_fee;
self.assets
.write()
.await
.get_mut(&synthetic_asset_id)
.unwrap()
.total_supply -= burn_amount;
Ok(fee_amount)
}
pub async fn add_collateral(
&self,
position_id: Uuid,
amount: Decimal,
) -> Result<(), &'static str> {
if amount <= Decimal::ZERO {
return Err("Amount must be positive");
}
let mut positions = self.positions.write().await;
let position = positions
.get_mut(&position_id)
.ok_or("Position not found")?;
position.collateral_amount += amount;
position.last_update = chrono::Utc::now().timestamp();
Ok(())
}
pub async fn withdraw_collateral(
&self,
position_id: Uuid,
amount: Decimal,
) -> Result<(), &'static str> {
if amount <= Decimal::ZERO {
return Err("Amount must be positive");
}
let positions = self.positions.read().await;
let position = positions.get(&position_id).ok_or("Position not found")?;
if amount > position.collateral_amount {
return Err("Insufficient collateral");
}
let synthetic_asset_id = position.synthetic_asset_id;
let collateral_asset_id = position.collateral_asset_id;
drop(positions);
let assets = self.assets.read().await;
let asset = assets.get(&synthetic_asset_id).unwrap();
let required_ratio = asset.collateral_ratio;
drop(assets);
let collateral_price = self
.get_price(collateral_asset_id)
.await
.ok_or("Collateral price not available")?;
let synthetic_price = self
.get_price(synthetic_asset_id)
.await
.ok_or("Synthetic price not available")?;
let positions = self.positions.read().await;
let position = positions.get(&position_id).unwrap();
let new_collateral = position.collateral_amount - amount;
let collateral_value = new_collateral * collateral_price;
let debt_value = position.minted_amount * synthetic_price;
if debt_value > Decimal::ZERO {
let ratio = collateral_value / debt_value;
if ratio < required_ratio {
return Err("Withdrawal would under-collateralize position");
}
}
drop(positions);
let mut positions = self.positions.write().await;
let position = positions.get_mut(&position_id).unwrap();
position.collateral_amount -= amount;
position.last_update = chrono::Utc::now().timestamp();
Ok(())
}
pub async fn liquidate(
&self,
liquidator_id: Uuid,
position_id: Uuid,
debt_to_cover: Decimal,
) -> Result<SyntheticLiquidation, &'static str> {
let positions = self.positions.read().await;
let position = positions.get(&position_id).ok_or("Position not found")?;
let synthetic_asset_id = position.synthetic_asset_id;
let collateral_asset_id = position.collateral_asset_id;
drop(positions);
let assets = self.assets.read().await;
let asset = assets.get(&synthetic_asset_id).unwrap();
let liquidation_ratio = asset.liquidation_ratio;
drop(assets);
let collateral_price = self
.get_price(collateral_asset_id)
.await
.ok_or("Collateral price not available")?;
let synthetic_price = self
.get_price(synthetic_asset_id)
.await
.ok_or("Synthetic price not available")?;
let positions = self.positions.read().await;
let position = positions.get(&position_id).unwrap();
if position.is_safe(collateral_price, synthetic_price, liquidation_ratio) {
return Err("Position is not liquidatable");
}
if debt_to_cover > position.minted_amount {
return Err("Debt to cover exceeds position debt");
}
drop(positions);
let liquidation_penalty = dec!(0.05);
let debt_value = debt_to_cover * synthetic_price;
let collateral_seized = (debt_value * (dec!(1) + liquidation_penalty)) / collateral_price;
let mut positions = self.positions.write().await;
let position = positions.get_mut(&position_id).unwrap();
position.collateral_amount -= collateral_seized;
position.minted_amount -= debt_to_cover;
position.last_update = chrono::Utc::now().timestamp();
self.assets
.write()
.await
.get_mut(&synthetic_asset_id)
.unwrap()
.total_supply -= debt_to_cover;
Ok(SyntheticLiquidation {
liquidation_id: Uuid::new_v4(),
position_id,
liquidator_id,
debt_covered: debt_to_cover,
collateral_seized,
liquidation_penalty: debt_value * liquidation_penalty,
timestamp: chrono::Utc::now().timestamp(),
})
}
pub async fn get_asset(&self, asset_id: Uuid) -> Option<SyntheticAsset> {
self.assets.read().await.get(&asset_id).cloned()
}
pub async fn get_position(&self, position_id: Uuid) -> Option<SyntheticPosition> {
self.positions.read().await.get(&position_id).cloned()
}
pub async fn get_user_positions(&self, user_id: Uuid) -> Vec<SyntheticPosition> {
let user_positions = self.user_positions.read().await;
let position_ids = user_positions.get(&user_id);
if let Some(ids) = position_ids {
let positions = self.positions.read().await;
ids.iter()
.filter_map(|id| positions.get(id).cloned())
.collect()
} else {
Vec::new()
}
}
}
impl Default for SyntheticAssetManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_synthetic_asset_creation() {
let asset = SyntheticAsset::new(
"sUSD".to_string(),
"Synthetic USD".to_string(),
SyntheticAssetType::Fiat,
dec!(1.5),
dec!(1.2),
dec!(0.003),
dec!(0.003),
);
assert!(asset.is_ok());
}
#[test]
fn test_required_collateral() {
let asset = SyntheticAsset::new(
"sUSD".to_string(),
"Synthetic USD".to_string(),
SyntheticAssetType::Fiat,
dec!(1.5),
dec!(1.2),
dec!(0.003),
dec!(0.003),
)
.unwrap();
let required = asset.required_collateral(dec!(100), dec!(1));
assert_eq!(required, dec!(150));
}
#[test]
fn test_collateral_ratio_calculation() {
let asset = SyntheticAsset::new(
"sUSD".to_string(),
"Synthetic USD".to_string(),
SyntheticAssetType::Fiat,
dec!(1.5),
dec!(1.2),
dec!(0.003),
dec!(0.003),
)
.unwrap();
let ratio = asset.calculate_ratio(dec!(200), dec!(100), dec!(1));
assert_eq!(ratio, dec!(2)); }
#[test]
fn test_position_safety() {
let position = SyntheticPosition::new(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4());
let mut test_pos = position.clone();
test_pos.collateral_amount = dec!(200);
test_pos.minted_amount = dec!(100);
assert!(test_pos.is_safe(dec!(1), dec!(1), dec!(1.5)));
test_pos.collateral_amount = dec!(140);
assert!(!test_pos.is_safe(dec!(1), dec!(1), dec!(1.5)));
}
#[tokio::test]
async fn test_mint_synthetic() {
let manager = SyntheticAssetManager::new();
let asset = SyntheticAsset::new(
"sUSD".to_string(),
"Synthetic USD".to_string(),
SyntheticAssetType::Fiat,
dec!(1.5),
dec!(1.2),
dec!(0.003),
dec!(0.003),
)
.unwrap();
let asset_id = asset.asset_id;
manager.create_asset(asset).await.unwrap();
let collateral_id = Uuid::new_v4();
manager
.update_price(PriceFeed {
asset_id: collateral_id,
price: dec!(1),
timestamp: chrono::Utc::now().timestamp(),
confidence: dec!(0.99),
})
.await;
manager
.update_price(PriceFeed {
asset_id,
price: dec!(1),
timestamp: chrono::Utc::now().timestamp(),
confidence: dec!(0.99),
})
.await;
let user_id = Uuid::new_v4();
let position_id = manager
.mint(user_id, asset_id, collateral_id, dec!(200), dec!(100))
.await
.unwrap();
let position = manager.get_position(position_id).await.unwrap();
assert_eq!(position.collateral_amount, dec!(200));
assert_eq!(position.minted_amount, dec!(100));
}
#[tokio::test]
async fn test_burn_synthetic() {
let manager = SyntheticAssetManager::new();
let asset = SyntheticAsset::new(
"sUSD".to_string(),
"Synthetic USD".to_string(),
SyntheticAssetType::Fiat,
dec!(1.5),
dec!(1.2),
dec!(0.003),
dec!(0.003),
)
.unwrap();
let asset_id = asset.asset_id;
manager.create_asset(asset).await.unwrap();
let collateral_id = Uuid::new_v4();
manager
.update_price(PriceFeed {
asset_id: collateral_id,
price: dec!(1),
timestamp: chrono::Utc::now().timestamp(),
confidence: dec!(0.99),
})
.await;
manager
.update_price(PriceFeed {
asset_id,
price: dec!(1),
timestamp: chrono::Utc::now().timestamp(),
confidence: dec!(0.99),
})
.await;
let user_id = Uuid::new_v4();
let position_id = manager
.mint(user_id, asset_id, collateral_id, dec!(200), dec!(100))
.await
.unwrap();
manager.burn(position_id, dec!(50)).await.unwrap();
let position = manager.get_position(position_id).await.unwrap();
assert_eq!(position.minted_amount, dec!(50));
}
}