429 lines
14 KiB
Rust
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());
|
|
}
|
|
}
|