//! Storage Deals - Economic layer for storage //! //! Users pay storage providers to store their data. //! Deals are registered on L1 for enforcement. use crate::cid::ContentId; use serde::{Deserialize, Serialize}; /// Storage deal status #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum DealStatus { /// Awaiting storage node acceptance Pending, /// Deal active, data being stored Active, /// Deal completed successfully Completed, /// Storage node failed proofs Failed, /// Deal cancelled by user Cancelled, } /// Storage deal between user and storage provider #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StorageDeal { /// Unique deal ID pub deal_id: [u8; 32], /// Content being stored pub cid: ContentId, /// Client address (paying for storage) pub client: [u8; 32], /// Storage provider node ID pub provider: Option<[u8; 32]>, /// Content size in bytes pub size: u64, /// Storage duration (in L1 blocks) pub duration: u64, /// Price per byte per epoch (in atomic SYNOR units) pub price_per_byte_epoch: u64, /// Total collateral locked by provider pub provider_collateral: u64, /// Client deposit pub client_deposit: u64, /// Deal start block (on L1) pub start_block: u64, /// Deal end block (on L1) pub end_block: u64, /// Current status pub status: DealStatus, /// Replication factor (how many nodes store the data) pub replication: u8, /// Failed proof count pub failed_proofs: u32, /// Successful proof count pub successful_proofs: u32, } impl StorageDeal { /// Calculate total cost for the deal pub fn total_cost(&self) -> u64 { let epochs = self.duration; self.size .saturating_mul(self.price_per_byte_epoch) .saturating_mul(epochs) .saturating_mul(self.replication as u64) } /// Check if deal is active pub fn is_active(&self) -> bool { self.status == DealStatus::Active } /// Check if deal has expired pub fn is_expired(&self, current_block: u64) -> bool { current_block >= self.end_block } /// Calculate provider reward for successful storage pub fn provider_reward(&self) -> u64 { // Provider gets paid based on successful proofs let total_possible_proofs = self.duration / 100; // Assume proof every 100 blocks if total_possible_proofs == 0 { return 0; } let success_rate = self.successful_proofs as u64 * 100 / total_possible_proofs; self.total_cost() * success_rate / 100 } } /// Request to create a new storage deal #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateDealRequest { /// Content to store pub cid: ContentId, /// Content size pub size: u64, /// Desired duration (blocks) pub duration: u64, /// Maximum price willing to pay pub max_price_per_byte_epoch: u64, /// Desired replication factor pub replication: u8, /// Preferred regions (optional) pub preferred_regions: Vec, } /// Storage provider offer #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StorageOffer { /// Provider node ID pub provider: [u8; 32], /// Available capacity (bytes) pub available_capacity: u64, /// Price per byte per epoch pub price_per_byte_epoch: u64, /// Minimum deal duration pub min_duration: u64, /// Maximum deal duration pub max_duration: u64, /// Regions served pub regions: Vec, /// Provider stake amount pub stake: u64, /// Historical uptime percentage pub uptime: u8, /// Success rate (proofs passed / total) pub success_rate: u8, } /// Storage market for matching clients with providers #[derive(Debug, Default)] pub struct StorageMarket { /// Active deals pub deals: Vec, /// Available offers from providers pub offers: Vec, } impl StorageMarket { /// Create a new storage market pub fn new() -> Self { Self::default() } /// Find best providers for a deal request pub fn find_providers(&self, request: &CreateDealRequest) -> Vec<&StorageOffer> { self.offers .iter() .filter(|offer| { offer.available_capacity >= request.size && offer.price_per_byte_epoch <= request.max_price_per_byte_epoch && offer.min_duration <= request.duration && offer.max_duration >= request.duration }) .collect() } /// Add a storage offer pub fn add_offer(&mut self, offer: StorageOffer) { self.offers.push(offer); } /// Create a deal from request and accepted offer pub fn create_deal( &mut self, request: CreateDealRequest, provider: [u8; 32], client: [u8; 32], current_block: u64, ) -> StorageDeal { let deal_id = self.generate_deal_id(&request.cid, &client, current_block); let offer = self.offers.iter().find(|o| o.provider == provider); let price = offer.map(|o| o.price_per_byte_epoch).unwrap_or(0); let deal = StorageDeal { deal_id, cid: request.cid, client, provider: Some(provider), size: request.size, duration: request.duration, price_per_byte_epoch: price, provider_collateral: 0, // Set during acceptance client_deposit: 0, // Set during creation start_block: current_block, end_block: current_block + request.duration, status: DealStatus::Pending, replication: request.replication, failed_proofs: 0, successful_proofs: 0, }; self.deals.push(deal.clone()); deal } fn generate_deal_id(&self, cid: &ContentId, client: &[u8; 32], block: u64) -> [u8; 32] { let mut input = Vec::new(); input.extend_from_slice(&cid.digest); input.extend_from_slice(client); input.extend_from_slice(&block.to_le_bytes()); *blake3::hash(&input).as_bytes() } } /// Pricing tiers for storage #[derive(Debug, Clone)] pub struct PricingTier { /// Tier name pub name: String, /// Price per GB per month (in atomic SYNOR) pub price_per_gb_month: u64, /// Minimum replication pub min_replication: u8, /// SLA uptime guarantee in basis points (10000 = 100%) pub uptime_sla_bps: u16, } impl PricingTier { /// Standard tier - basic storage pub fn standard() -> Self { Self { name: "Standard".to_string(), price_per_gb_month: 100_000_000, // 0.1 SYNOR per GB/month min_replication: 3, uptime_sla_bps: 9900, // 99.00% } } /// Premium tier - higher redundancy pub fn premium() -> Self { Self { name: "Premium".to_string(), price_per_gb_month: 250_000_000, // 0.25 SYNOR per GB/month min_replication: 5, uptime_sla_bps: 9990, // 99.90% } } /// Permanent tier - one-time payment for ~20 years pub fn permanent() -> Self { Self { name: "Permanent".to_string(), price_per_gb_month: 2_400_000_000, // 2.4 SYNOR per GB (one-time, ~20 years equiv) min_replication: 10, uptime_sla_bps: 9999, // 99.99% } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_deal_cost() { let deal = StorageDeal { deal_id: [0u8; 32], cid: ContentId::from_content(b"test"), client: [1u8; 32], provider: Some([2u8; 32]), size: 1_000_000_000, // 1 GB duration: 100, price_per_byte_epoch: 1, provider_collateral: 0, client_deposit: 0, start_block: 0, end_block: 100, status: DealStatus::Active, replication: 3, failed_proofs: 0, successful_proofs: 0, }; let cost = deal.total_cost(); assert_eq!(cost, 1_000_000_000 * 100 * 3); } #[test] fn test_market_find_providers() { let mut market = StorageMarket::new(); market.add_offer(StorageOffer { provider: [1u8; 32], available_capacity: 100_000_000_000, // 100 GB price_per_byte_epoch: 1, min_duration: 10, max_duration: 10000, regions: vec!["US".to_string()], stake: 1000, uptime: 99, success_rate: 99, }); let request = CreateDealRequest { cid: ContentId::from_content(b"test"), size: 1_000_000, duration: 100, max_price_per_byte_epoch: 2, replication: 3, preferred_regions: vec![], }; let providers = market.find_providers(&request); assert_eq!(providers.len(), 1); } #[test] fn test_pricing_tiers() { let standard = PricingTier::standard(); let premium = PricingTier::premium(); let permanent = PricingTier::permanent(); assert!(standard.price_per_gb_month < premium.price_per_gb_month); assert!(premium.price_per_gb_month < permanent.price_per_gb_month); assert!(standard.min_replication < premium.min_replication); } }