use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tenzro_types::{AssetId, AssetInfo, AssetType, StablecoinType};
pub struct AssetManager {
assets: DashMap<AssetId, AssetInfo>,
}
impl AssetManager {
pub fn new() -> Self {
let manager = Self {
assets: DashMap::new(),
};
manager.register_default_assets();
manager
}
fn register_default_assets(&self) {
self.register_asset(AssetInfo::tnzo());
self.register_asset(AssetInfo::new(
AssetId::from("USDT"),
"Tether USD".to_string(),
"USDT".to_string(),
6,
0, AssetType::Stablecoin(StablecoinType::USD),
));
self.register_asset(AssetInfo::new(
AssetId::from("USDC"),
"USD Coin".to_string(),
"USDC".to_string(),
6,
0, AssetType::Stablecoin(StablecoinType::USD),
));
self.register_asset(AssetInfo::new(
AssetId::from("DAI"),
"DAI Stablecoin".to_string(),
"DAI".to_string(),
18,
0,
AssetType::Stablecoin(StablecoinType::Algorithmic),
));
}
pub fn register_asset(&self, asset_info: AssetInfo) {
self.assets.insert(asset_info.asset_id.clone(), asset_info);
}
pub fn get_asset(&self, asset_id: &AssetId) -> Option<AssetInfo> {
self.assets.get(asset_id).map(|entry| entry.value().clone())
}
pub fn is_supported(&self, asset_id: &AssetId) -> bool {
self.assets.contains_key(asset_id)
}
pub fn list_assets(&self) -> Vec<AssetInfo> {
self.assets
.iter()
.map(|entry| entry.value().clone())
.collect()
}
pub fn list_assets_by_type(&self, asset_type: AssetType) -> Vec<AssetInfo> {
self.assets
.iter()
.filter(|entry| entry.value().asset_type == asset_type)
.map(|entry| entry.value().clone())
.collect()
}
pub fn list_stablecoins(&self) -> Vec<AssetInfo> {
self.assets
.iter()
.filter(|entry| matches!(entry.value().asset_type, AssetType::Stablecoin(_)))
.map(|entry| entry.value().clone())
.collect()
}
pub fn get_native_token(&self) -> AssetInfo {
self.get_asset(&AssetId::tnzo())
.expect("TNZO should always be registered")
}
pub fn unregister_asset(&self, asset_id: &AssetId) -> Option<AssetInfo> {
self.assets.remove(asset_id).map(|(_, info)| info)
}
pub fn asset_count(&self) -> usize {
self.assets.len()
}
pub fn clear(&self) {
self.assets.clear();
}
}
impl Default for AssetManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultAssetConfig {
pub default_assets: Vec<AssetId>,
pub auto_enable_stablecoins: bool,
}
impl DefaultAssetConfig {
pub fn new() -> Self {
Self {
default_assets: vec![
AssetId::tnzo(),
AssetId::from("USDT"),
AssetId::from("USDC"),
],
auto_enable_stablecoins: true,
}
}
pub fn get_default_assets(&self) -> &[AssetId] {
&self.default_assets
}
pub fn add_default_asset(&mut self, asset_id: AssetId) {
if !self.default_assets.contains(&asset_id) {
self.default_assets.push(asset_id);
}
}
pub fn remove_default_asset(&mut self, asset_id: &AssetId) {
self.default_assets.retain(|a| a != asset_id);
}
}
impl Default for DefaultAssetConfig {
fn default() -> Self {
Self::new()
}
}
pub type SharedAssetManager = Arc<AssetManager>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_asset_manager_default_assets() {
let manager = AssetManager::new();
assert!(manager.is_supported(&AssetId::tnzo()));
assert!(manager.is_supported(&AssetId::from("USDT")));
assert!(manager.is_supported(&AssetId::from("USDC")));
assert!(manager.is_supported(&AssetId::from("DAI")));
assert!(manager.asset_count() >= 4);
}
#[test]
fn test_register_asset() {
let manager = AssetManager::new();
let custom_asset = AssetInfo::new(
AssetId::from("TEST"),
"Test Token".to_string(),
"TEST".to_string(),
18,
1000000,
AssetType::Fungible,
);
manager.register_asset(custom_asset.clone());
assert!(manager.is_supported(&AssetId::from("TEST")));
let retrieved = manager.get_asset(&AssetId::from("TEST")).unwrap();
assert_eq!(retrieved.name, "Test Token");
assert_eq!(retrieved.symbol, "TEST");
}
#[test]
fn test_list_assets() {
let manager = AssetManager::new();
let assets = manager.list_assets();
assert!(!assets.is_empty());
let asset_ids: Vec<_> = assets.iter().map(|a| &a.asset_id).collect();
assert!(asset_ids.contains(&&AssetId::tnzo()));
}
#[test]
fn test_list_stablecoins() {
let manager = AssetManager::new();
let stablecoins = manager.list_stablecoins();
assert!(!stablecoins.is_empty());
for asset in stablecoins {
assert!(matches!(asset.asset_type, AssetType::Stablecoin(_)));
}
}
#[test]
fn test_native_token() {
let manager = AssetManager::new();
let native = manager.get_native_token();
assert_eq!(native.asset_id, AssetId::tnzo());
assert_eq!(native.symbol, "TNZO");
assert_eq!(native.asset_type, AssetType::Native);
}
#[test]
fn test_unregister_asset() {
let manager = AssetManager::new();
let custom_asset = AssetInfo::new(
AssetId::from("TEST"),
"Test Token".to_string(),
"TEST".to_string(),
18,
1000000,
AssetType::Fungible,
);
manager.register_asset(custom_asset);
assert!(manager.is_supported(&AssetId::from("TEST")));
let removed = manager.unregister_asset(&AssetId::from("TEST"));
assert!(removed.is_some());
assert!(!manager.is_supported(&AssetId::from("TEST")));
}
#[test]
fn test_list_assets_by_type() {
let manager = AssetManager::new();
let native_assets = manager.list_assets_by_type(AssetType::Native);
assert_eq!(native_assets.len(), 1);
assert_eq!(native_assets[0].asset_id, AssetId::tnzo());
let stablecoins = manager.list_assets_by_type(AssetType::Stablecoin(StablecoinType::USD));
assert!(!stablecoins.is_empty());
}
#[test]
fn test_default_asset_config() {
let config = DefaultAssetConfig::new();
assert!(config.get_default_assets().contains(&AssetId::tnzo()));
assert!(config.get_default_assets().contains(&AssetId::from("USDT")));
assert!(config.auto_enable_stablecoins);
}
#[test]
fn test_modify_default_config() {
let mut config = DefaultAssetConfig::new();
let custom_asset = AssetId::from("CUSTOM");
config.add_default_asset(custom_asset.clone());
assert!(config.get_default_assets().contains(&custom_asset));
config.remove_default_asset(&custom_asset);
assert!(!config.get_default_assets().contains(&custom_asset));
}
}