synor/crates/synor-economics/src/lib.rs
2026-02-02 05:58:22 +05:30

429 lines
14 KiB
Rust

//! # Synor Economics
//!
//! Economics, pricing, metering, and billing infrastructure for Synor L2 services.
//!
//! ## Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │ SYNOR ECONOMICS LAYER │
//! ├─────────────────────────────────────────────────────────────┤
//! │ Billing Engine: Invoice → Payment → Credit │
//! │ Metering: Storage | Hosting | Database | Compute | Network │
//! │ Pricing: Oracle → TWAP → Tiers → Discounts │
//! └─────────────────────────────────────────────────────────────┘
//! ```
//!
//! ## Components
//!
//! - **Pricing Oracle**: Aggregates SYNOR/USD prices from multiple sources
//! - **Metering Service**: Tracks real-time resource usage across L2 services
//! - **Billing Engine**: Generates invoices, processes payments, manages credits
//! - **Cost Calculator**: Estimates costs before resource consumption
pub mod billing;
pub mod calculator;
pub mod error;
pub mod metering;
pub mod oracle;
pub mod pricing;
pub use billing::{BillingEngine, Credit, Invoice, InvoiceStatus, Payment, PaymentMethod};
pub use calculator::{CostEstimate, CostEstimator, UsageProjection};
pub use error::{EconomicsError, Result};
pub use metering::{
ComputeUsage, DatabaseUsage, HostingUsage, MeteringService, NetworkUsage, StorageUsage,
UsageEvent, UsageRecord,
};
pub use oracle::{
AggregateFeed, ChainlinkFeed, CoinGeckoFeed, InternalDexFeed, MockPriceFeed, OracleFactory,
PriceFeed, PriceOracle, PriceOracleBuilder, PriceSource, ProductionOracleConfig, TokenPrice,
};
pub use pricing::{Discount, DiscountType, PricingEngine, PricingTier, ServicePricing};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
/// Synor token amount in smallest unit (1 SYNOR = 10^18 units)
pub type SynorAmount = u128;
/// SYNOR decimal representation (for UI display)
pub type SynorDecimal = Decimal;
/// Account identifier
pub type AccountId = String;
/// Service identifier
pub type ServiceId = String;
/// Fee distribution configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeDistribution {
/// Percentage burned (deflationary)
pub burn_rate: Decimal,
/// Percentage to stakers
pub staker_rate: Decimal,
/// Percentage to community treasury
pub treasury_rate: Decimal,
/// Percentage to validators/node operators
pub operator_rate: Decimal,
}
impl Default for FeeDistribution {
fn default() -> Self {
Self::transaction_fees()
}
}
impl FeeDistribution {
/// Transaction fee distribution (L1)
pub fn transaction_fees() -> Self {
Self {
burn_rate: Decimal::new(10, 2), // 10%
staker_rate: Decimal::new(60, 2), // 60%
treasury_rate: Decimal::new(20, 2), // 20%
operator_rate: Decimal::new(10, 2), // 10%
}
}
/// L2 service fee distribution
pub fn l2_service_fees() -> Self {
Self {
burn_rate: Decimal::new(10, 2), // 10%
staker_rate: Decimal::ZERO, // 0%
treasury_rate: Decimal::new(20, 2), // 20%
operator_rate: Decimal::new(70, 2), // 70%
}
}
/// Calculate distribution amounts from a total fee
pub fn distribute(&self, total: SynorDecimal) -> FeeBreakdown {
FeeBreakdown {
burn: total * self.burn_rate,
stakers: total * self.staker_rate,
treasury: total * self.treasury_rate,
operators: total * self.operator_rate,
total,
}
}
/// Validate that rates sum to 100%
pub fn validate(&self) -> Result<()> {
let sum = self.burn_rate + self.staker_rate + self.treasury_rate + self.operator_rate;
if sum != Decimal::ONE {
return Err(EconomicsError::InvalidFeeDistribution(format!(
"Rates must sum to 100%, got {}%",
sum * Decimal::ONE_HUNDRED
)));
}
Ok(())
}
}
/// Breakdown of fee distribution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeBreakdown {
pub burn: SynorDecimal,
pub stakers: SynorDecimal,
pub treasury: SynorDecimal,
pub operators: SynorDecimal,
pub total: SynorDecimal,
}
/// Service type for metering and billing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ServiceType {
/// Storage L2
Storage,
/// Web hosting
Hosting,
/// Database L2
Database,
/// Compute L2 (CPU/GPU/TPU)
Compute,
/// Network bandwidth
Network,
/// Smart contract execution
Contract,
}
impl ServiceType {
/// Get the fee distribution model for this service type
pub fn fee_distribution(&self) -> FeeDistribution {
match self {
ServiceType::Contract => FeeDistribution::transaction_fees(),
_ => FeeDistribution::l2_service_fees(),
}
}
}
impl std::fmt::Display for ServiceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ServiceType::Storage => write!(f, "storage"),
ServiceType::Hosting => write!(f, "hosting"),
ServiceType::Database => write!(f, "database"),
ServiceType::Compute => write!(f, "compute"),
ServiceType::Network => write!(f, "network"),
ServiceType::Contract => write!(f, "contract"),
}
}
}
/// Resource unit for pricing and metering
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResourceUnit {
/// Storage: bytes
Bytes,
/// Storage: gigabytes per month
GbMonth,
/// Database: queries
Queries,
/// Database: vector searches
VectorSearches,
/// Compute: CPU core hours
CpuCoreHours,
/// Compute: GPU hours
GpuHours,
/// Compute: memory GB hours
MemoryGbHours,
/// Compute: serverless invocations
Invocations,
/// Network: bandwidth in GB
BandwidthGb,
/// Hosting: custom domains
Domains,
/// Contract: gas units
Gas,
}
impl std::fmt::Display for ResourceUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourceUnit::Bytes => write!(f, "bytes"),
ResourceUnit::GbMonth => write!(f, "GB/month"),
ResourceUnit::Queries => write!(f, "queries"),
ResourceUnit::VectorSearches => write!(f, "vector searches"),
ResourceUnit::CpuCoreHours => write!(f, "CPU core hours"),
ResourceUnit::GpuHours => write!(f, "GPU hours"),
ResourceUnit::MemoryGbHours => write!(f, "memory GB hours"),
ResourceUnit::Invocations => write!(f, "invocations"),
ResourceUnit::BandwidthGb => write!(f, "GB bandwidth"),
ResourceUnit::Domains => write!(f, "domains"),
ResourceUnit::Gas => write!(f, "gas"),
}
}
}
/// Account balance and usage summary
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountSummary {
pub account_id: AccountId,
pub balance: SynorDecimal,
pub credit_balance: SynorDecimal,
pub prepaid_balance: SynorDecimal,
pub current_period_usage: HashMap<ServiceType, SynorDecimal>,
pub current_period_cost: SynorDecimal,
pub billing_tier: String,
pub last_payment: Option<DateTime<Utc>>,
pub next_invoice_date: Option<DateTime<Utc>>,
}
/// Unified economics manager combining all services
pub struct EconomicsManager {
pub oracle: Arc<RwLock<PriceOracle>>,
pub metering: Arc<MeteringService>,
pub billing: Arc<BillingEngine>,
pub pricing: Arc<PricingEngine>,
pub calculator: Arc<CostEstimator>,
}
impl EconomicsManager {
/// Create a new economics manager with default configuration
/// Uses a development oracle with mock price feeds
pub fn new() -> Self {
use rust_decimal_macros::dec;
// Default to development oracle with mock feeds at $1.50 base price
let oracle = Arc::new(RwLock::new(oracle::OracleFactory::development(dec!(1.50))));
let pricing = Arc::new(PricingEngine::new());
let metering = Arc::new(MeteringService::new(pricing.clone()));
let billing = Arc::new(BillingEngine::new(metering.clone(), pricing.clone()));
let calculator = Arc::new(CostEstimator::new(pricing.clone()));
Self {
oracle,
metering,
billing,
pricing,
calculator,
}
}
/// Create an economics manager with production oracle configuration
pub fn with_production_oracle(config: oracle::ProductionOracleConfig) -> Self {
let oracle = Arc::new(RwLock::new(oracle::OracleFactory::production(config)));
let pricing = Arc::new(PricingEngine::new());
let metering = Arc::new(MeteringService::new(pricing.clone()));
let billing = Arc::new(BillingEngine::new(metering.clone(), pricing.clone()));
let calculator = Arc::new(CostEstimator::new(pricing.clone()));
Self {
oracle,
metering,
billing,
pricing,
calculator,
}
}
/// Create an economics manager with a custom oracle
pub fn with_oracle(oracle: PriceOracle) -> Self {
let oracle = Arc::new(RwLock::new(oracle));
let pricing = Arc::new(PricingEngine::new());
let metering = Arc::new(MeteringService::new(pricing.clone()));
let billing = Arc::new(BillingEngine::new(metering.clone(), pricing.clone()));
let calculator = Arc::new(CostEstimator::new(pricing.clone()));
Self {
oracle,
metering,
billing,
pricing,
calculator,
}
}
/// Get account summary including balance, usage, and billing info
pub async fn get_account_summary(&self, account_id: &str) -> Result<AccountSummary> {
let usage = self.metering.get_account_usage(account_id).await?;
let billing_info = self.billing.get_account_info(account_id).await?;
let mut current_period_usage = HashMap::new();
let mut current_period_cost = Decimal::ZERO;
for (service_type, amount) in usage.by_service.iter() {
current_period_usage.insert(*service_type, *amount);
current_period_cost += *amount;
}
Ok(AccountSummary {
account_id: account_id.to_string(),
balance: billing_info.balance,
credit_balance: billing_info.credit_balance,
prepaid_balance: billing_info.prepaid_balance,
current_period_usage,
current_period_cost,
billing_tier: billing_info.tier_name,
last_payment: billing_info.last_payment,
next_invoice_date: billing_info.next_invoice,
})
}
/// Estimate cost for a usage projection
pub async fn estimate_cost(&self, projection: UsageProjection) -> Result<CostEstimate> {
self.calculator.estimate(projection).await
}
/// Record usage event
pub async fn record_usage(&self, event: UsageEvent) -> Result<()> {
self.metering.record(event).await
}
/// Update price from external feed
pub async fn update_price(&self, price: TokenPrice) -> Result<()> {
let mut oracle = self.oracle.write().await;
oracle.update_price(price)
}
/// Get current SYNOR/USD price
pub async fn get_synor_price(&self) -> Result<SynorDecimal> {
let oracle = self.oracle.read().await;
oracle.get_price("SYNOR", "USD")
}
/// Generate invoice for account
pub async fn generate_invoice(&self, account_id: &str) -> Result<Invoice> {
self.billing.generate_invoice(account_id).await
}
/// Process payment
pub async fn process_payment(&self, payment: Payment) -> Result<()> {
self.billing.process_payment(payment).await
}
}
impl Default for EconomicsManager {
fn default() -> Self {
Self::new()
}
}
/// Convert raw amount to SYNOR decimal (18 decimals)
pub fn to_synor_decimal(raw: SynorAmount) -> SynorDecimal {
Decimal::from(raw) / Decimal::from(10u128.pow(18))
}
/// Convert SYNOR decimal to raw amount
pub fn from_synor_decimal(decimal: SynorDecimal) -> SynorAmount {
let scaled = decimal * Decimal::from(10u128.pow(18));
scaled.try_into().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_fee_distribution_transaction() {
let dist = FeeDistribution::transaction_fees();
assert!(dist.validate().is_ok());
let breakdown = dist.distribute(dec!(100));
assert_eq!(breakdown.burn, dec!(10));
assert_eq!(breakdown.stakers, dec!(60));
assert_eq!(breakdown.treasury, dec!(20));
assert_eq!(breakdown.operators, dec!(10));
}
#[test]
fn test_fee_distribution_l2() {
let dist = FeeDistribution::l2_service_fees();
assert!(dist.validate().is_ok());
let breakdown = dist.distribute(dec!(100));
assert_eq!(breakdown.burn, dec!(10));
assert_eq!(breakdown.stakers, dec!(0));
assert_eq!(breakdown.treasury, dec!(20));
assert_eq!(breakdown.operators, dec!(70));
}
#[test]
fn test_synor_decimal_conversion() {
// 1 SYNOR = 10^18 units
let one_synor = 1_000_000_000_000_000_000u128;
let decimal = to_synor_decimal(one_synor);
assert_eq!(decimal, Decimal::ONE);
let back = from_synor_decimal(decimal);
assert_eq!(back, one_synor);
}
#[test]
fn test_invalid_fee_distribution() {
let invalid = FeeDistribution {
burn_rate: dec!(0.50),
staker_rate: dec!(0.50),
treasury_rate: dec!(0.10),
operator_rate: dec!(0.10),
};
assert!(invalid.validate().is_err());
}
}