//! # Vendor copy temporal: `OitFeeSchedule` (Mathi `arkhe-amis-server` Sprint Mathi-XIII) //! //! ## ⚠️ TEMPORARY VENDOR COPY ⚠️ //! //! Will be replaced when: //! 1. Mathi ships `arkhe-amis-server` como standalone crates.io release, OR //! 2. Post-OCAFB ratify 7/7 (deadline 2026-06-21), Mathi confirma swap canonical //! cross-workspace via `[workspace.dependencies]` resolution //! //! Per OCAFB-1..6 sub-decisions del joint DC `origin_chain_amis_fee_bridge_v1` //! (`drawer_wing_palace_design_contracts_f024b8611caab85cc8a5ed5a`) abierto //! 2026-05-24 (Mathi+Charon joint, 7 signers, ratify deadline 2026-06-21): //! //! - **OCAFB-1**: `OitFeeSchedule` trait 4-method dyn-compatible (Mathi authority Level A) //! - **OCAFB-3**: `subsidy_basis_points(hw, action) -> u32` matrix Inmutable #16 anti-clasista //! - **OCAFB-4**: `entitlement_unlocked(tier, action) -> bool` Inmutable #37 + #18 enforcement //! - **OCAFB-5**: `compute_fee(base, hw, sub, action) -> u64` 3-step formula canonical //! - **OCAFB-6**: `OitFeeScheduleMock` builder pattern + `arc_mock` test consumer harness //! //! Source ratificada Mathi: commit `ce4fafb` (2026-05-24T10:28Z) en //! `crates/arkhe-amis/crates/arkhe-amis-server/src/fee_schedule.rs` (536 LOC). //! //! **Adaptaciones canonical**: //! - `ChainAction`: consumed desde `origin_chain_types::actions::ChainAction` (source-of-truth //! per OCAFB-2, NO vendor local — pallet usa el canonical upstream) //! - `HardwareTier`: consumed desde `origin_chain_types::tiers::HardwareTier` (mirror Mathi) //! - `SubscriptionTier`: consumed desde `origin_chain_types::tiers::SubscriptionTier` (mirror Mathi) //! //! Cuando swap canonical happens: //! - DELETE este module entero canonical //! - Add dep: `arkhe-amis-server = { version = "0.X", default-features = false }` //! - Update re-exports en `lib.rs` canonical //! - Diary canonical Mathi coordination switch extern crate alloc; use alloc::sync::Arc; use origin_chain_types::{ChainAction, HardwareTier, SubscriptionTier}; // ─── OitFeeSchedule trait (OCAFB-1) ────────────────────────────────── /// Surface fee modulation cross-pillar Pilar 1 AMIS ↔ Pilar 4 Origin Chain. /// /// Per OCAFB-1 sub-decision del joint DC `origin_chain_amis_fee_bridge_v1`: /// 4 methods firmes que `pallet-origin-fees` (Charon Phase 2.4) consume via /// `Arc`. /// /// **`Send + Sync`**: shared cross-thread between Charon's substrate runtime /// + Mathi's AMIS query handlers + Irhis Web pricing display thread. /// **dyn-compatible**: pallet holds `Arc` per ADR-0057 /// trait-seam-first pattern. pub trait OitFeeSchedule: Send + Sync { /// Tier suscripción actual del user (OCAFB-4 entitlement basis). /// Default impl produce `SubscriptionTier::Free` per Inmutable #37. fn current_subscription_tier(&self, user_id: &str) -> SubscriptionTier; /// Tier hardware actual del user (OCAFB-3 subsidy basis). /// Default impl produce `HardwareTier::EmbeddedArm` (safe baseline /// Pi 5 default per Inmutable #16 anti-clasista). fn current_hardware_tier(&self, user_id: &str) -> HardwareTier; /// `true` cuando user califica para subsidy 100% por (HardwareTier × /// ChainAction) per OCAFB-3 matrix + override `educational_world`. /// Inmutable #16 strong materializado. fn subsidy_eligible(&self, user_id: &str, action: ChainAction) -> bool; /// Multiplicador fee en basis points (10000 = 100%, 1000 = 10%). /// Combina HardwareTier subsidy + SubscriptionTier upgrade per OCAFB-5 /// formula. `pallet-origin-fees` consume con `base_fee.saturating_mul(mult) / 10000`. fn tier_multiplier(&self, user_id: &str, action: ChainAction) -> u64; } // ─── OCAFB-3 subsidy matrix (HardwareTier × ChainAction) ──────────── /// Matrix subsidy basis points firme per OCAFB-3 Inmutable #16 anti-clasista. /// Valores: 10000 = 100% subsidy (free), 0 = no subsidy (pay full fee). /// /// David canon ratificado 2026-05-14: *"La libertad económica del Pi 5 user /// en Sub-Saharan Africa NO se acota por el Ultra user en San Francisco"*. pub const fn subsidy_basis_points(hw: HardwareTier, action: ChainAction) -> u32 { use ChainAction::*; use HardwareTier::*; match (hw, action) { // Read action: siempre 100% subsidy (Inmutable #37 baseline) (_, Read) => 10000, // EmbeddedArm (Pi 5): subsidy alta — anti-clasista from block 0 (EmbeddedArm, Write) => 9000, (EmbeddedArm, Compute) => 10000, // educational compute free (EmbeddedArm, Transfer) => 9000, // EmbeddedAI (Jetson Orin Nano): MISMO piso EmbeddedArm — Inmutable #16, // el estudiante en Jetson no paga más que en Pi 5. Ergon bbac1b2. (EmbeddedAI, Write) => 9000, (EmbeddedAI, Compute) => 10000, // educational compute free (EmbeddedAI, Transfer) => 9000, // Portable: subsidy media (Portable, Write) => 7500, (Portable, Compute) => 5000, (Portable, Transfer) => 7500, // Desktop: subsidy moderada (Desktop, Write) => 2500, (Desktop, Compute) => 2500, (Desktop, Transfer) => 2500, // Ultra: full price (no subsidy) (Ultra, Write) => 0, (Ultra, Compute) => 0, (Ultra, Transfer) => 0, // Phase 3 SBS-5 contract actions — anti-clasista mapping interim // (Slice 3.1 SCAFFOLD v0.0.37). Mappeo conservativo análogo a Write // (DeployContract + UploadContractCode = storage-heavy) y Compute // (CallContract = PolkaVM gas execution). Refinement Slice 3.2 cuando // Mathi extiende OitFeeSchedule con measurements empíricos gas/deposit // reales post-Phase 3 Ship 2 wired runtime Index 11. (EmbeddedArm, DeployContract) => 9000, // Pi 5 dev contracts canonical (EmbeddedArm, CallContract) => 10000, // educational call free (EmbeddedArm, UploadContractCode) => 9000, // Pi 5 storage subsidy // EmbeddedAI (Jetson): mismo piso EmbeddedArm en contract actions (#16). (EmbeddedAI, DeployContract) => 9000, // Jetson dev contracts = piso Pi 5 (EmbeddedAI, CallContract) => 10000, // educational call free (EmbeddedAI, UploadContractCode) => 9000, // Jetson storage subsidy = piso (Portable, DeployContract) => 7500, (Portable, CallContract) => 5000, (Portable, UploadContractCode) => 7500, (Desktop, DeployContract) => 2500, (Desktop, CallContract) => 2500, (Desktop, UploadContractCode) => 2500, (Ultra, DeployContract) => 0, (Ultra, CallContract) => 0, (Ultra, UploadContractCode) => 0, } } // ─── OCAFB-4 entitlement matrix (SubscriptionTier × ChainAction) ──── /// `true` cuando user con `tier` puede ejecutar `action` per OCAFB-4 entitlement /// unlocks. Inmutable #37 Free perpetua baseline + Inmutable #18 EEG Ultra-only. pub const fn entitlement_unlocked(tier: SubscriptionTier, action: ChainAction) -> bool { use ChainAction::*; use SubscriptionTier::*; match (tier, action) { // Free: Read ALL + basic Write + basic Transfer (10 OGC/day) (Free, Read) => true, (Free, Write) => true, // basic operations only (caller validates limits) (Free, Compute) => false, // blocked — upgrade required (Free, Transfer) => true, // basic ≤10 OGC/day // Pro: ALL except advanced Compute (Ultra-only) (Pro, Read) => true, (Pro, Write) => true, (Pro, Compute) => true, // basic compute unlocked (Pro, Transfer) => true, // Phase 3 SBS-5 contract actions Free tier — conservativo interim // (Slice 3.1 SCAFFOLD v0.0.37). Free user blocked de smart contract // operations por default. Refinement Slice 3.2 con Mathi: educational // world override + Pi 5 dev path canonical via subscription upgrade // gratuita post-OCAFB ratify Phase 3 maduración. (Free, DeployContract) => false, // requires Pro+ initial (Free, CallContract) => false, // requires Pro+ initial (Free, UploadContractCode) => false, // requires Pro+ initial (Pro, DeployContract) => true, (Pro, CallContract) => true, (Pro, UploadContractCode) => true, // Ultra: ALL incluyendo EEG Compute (Inmutable #18) y contracts (Ultra, _) => true, } } // ─── OCAFB-5 fee compute formula ──────────────────────────────────── /// Compute fee per OCAFB-5 formula: /// 1. Si subsidy 100% → return 0 (Inmutable #16 strong) /// 2. Si entitlement blocked → return u64::MAX (Inmutable #37 enforcement) /// 3. `base × hw_mult × sub_mult / 10000` (basis points math) /// /// Aligned canonical ORIGIN_BUSINESS_MODEL_V2 D1-D6 ratificada 2026-05-18. pub fn compute_fee( base: u64, hw: HardwareTier, sub: SubscriptionTier, action: ChainAction, ) -> u64 { // Step 1: subsidy 100% — Inmutable #16 strong let subsidy_bp = subsidy_basis_points(hw, action); if subsidy_bp >= 10000 { return 0; } // Step 2: entitlement blocked — Inmutable #37 enforcement if !entitlement_unlocked(sub, action) { return u64::MAX; } // Step 3: basis points math (consume SubscriptionTier::multiplier_basis_points canonical) let hw_mult = 10000u64.saturating_sub(subsidy_bp as u64); // remainder post-subsidy let sub_mult = sub.multiplier_basis_points(); base.saturating_mul(hw_mult) .saturating_mul(sub_mult) / 10000 / 10000 } // ─── OitFeeScheduleMock (OCAFB-6 test harness) ───────────────────── /// Mock impl firme para test consumer harness OCAFB-6 — `pallet-origin-fees` /// Charon tests consume via `Arc` sin necesitar backend real. /// /// Configurable per-user state via builder pattern. Default produce Free + /// EmbeddedArm para todos (safe baseline Inmutable #37 + #16). #[derive(Debug, Clone)] pub struct OitFeeScheduleMock { default_subscription: SubscriptionTier, default_hardware: HardwareTier, educational_world: bool, } impl Default for OitFeeScheduleMock { fn default() -> Self { Self { default_subscription: SubscriptionTier::Free, default_hardware: HardwareTier::EmbeddedArm, educational_world: false, } } } impl OitFeeScheduleMock { /// Constructor con defaults baseline (Free + EmbeddedArm + no override). pub fn new() -> Self { Self::default() } /// Override SubscriptionTier default para tests. pub fn with_subscription(mut self, tier: SubscriptionTier) -> Self { self.default_subscription = tier; self } /// Override HardwareTier default para tests. pub fn with_hardware(mut self, tier: HardwareTier) -> Self { self.default_hardware = tier; self } /// Habilita override `educational_world` → 100% subsidy EmbeddedArm + Portable /// (David canon ratificado: *"la niña en escuela rural recibe el mismo Lab /// Faraday que un estudiante Stanford Ultra"*). pub fn with_educational_world(mut self, enabled: bool) -> Self { self.educational_world = enabled; self } } impl OitFeeSchedule for OitFeeScheduleMock { fn current_subscription_tier(&self, _user_id: &str) -> SubscriptionTier { self.default_subscription } fn current_hardware_tier(&self, _user_id: &str) -> HardwareTier { self.default_hardware } fn subsidy_eligible(&self, _user_id: &str, action: ChainAction) -> bool { // OCAFB-3 override: educational_world → 100% subsidy en los tiers floor // EmbeddedArm + EmbeddedAI + Portable (Jetson = device de estudiante, #16). if self.educational_world && matches!( self.default_hardware, HardwareTier::EmbeddedArm | HardwareTier::EmbeddedAI | HardwareTier::Portable ) { return true; } subsidy_basis_points(self.default_hardware, action) >= 10000 } fn tier_multiplier(&self, _user_id: &str, action: ChainAction) -> u64 { // Educational override → multiplier 0 (free) en los tiers floor. if self.educational_world && matches!( self.default_hardware, HardwareTier::EmbeddedArm | HardwareTier::EmbeddedAI | HardwareTier::Portable ) { return 0; } // Composite hw + sub multiplier per OCAFB-5 step 3 let subsidy_bp = subsidy_basis_points(self.default_hardware, action); let hw_mult = 10000u64.saturating_sub(subsidy_bp as u64); let sub_mult = self.default_subscription.multiplier_basis_points(); hw_mult.saturating_mul(sub_mult) / 10000 } } /// Helper para Charon consumer pattern — wrap mock en `Arc`. pub fn arc_mock(mock: OitFeeScheduleMock) -> Arc { Arc::new(mock) } // ─── Tests OCAFB-6 minimum 7 scenarios + 3 lobo blanco ───────────── #[cfg(test)] mod tests { use super::*; // ─── OCAFB-3 subsidy matrix ─────────────────────────────────── #[test] fn ocafb_3_read_always_full_subsidy_canonical() { // Inmutable #37 baseline: Read NEVER charged per HardwareTier for hw in [ HardwareTier::EmbeddedArm, HardwareTier::EmbeddedAI, HardwareTier::Portable, HardwareTier::Desktop, HardwareTier::Ultra, ] { assert_eq!(subsidy_basis_points(hw, ChainAction::Read), 10000); } } #[test] fn embedded_ai_subsidy_mirrors_embedded_arm_floor_inmutable_16() { // El estudiante en Jetson (EmbeddedAI) no paga MÁS que en Pi 5 // (EmbeddedArm): piso idéntico en TODA acción. Ergon bbac1b2, #16. for action in [ ChainAction::Read, ChainAction::Write, ChainAction::Compute, ChainAction::Transfer, ChainAction::DeployContract, ChainAction::CallContract, ChainAction::UploadContractCode, ] { assert_eq!( subsidy_basis_points(HardwareTier::EmbeddedAI, action), subsidy_basis_points(HardwareTier::EmbeddedArm, action), "EmbeddedAI debe espejar el piso EmbeddedArm en {action:?}" ); } } #[test] fn ocafb_3_ultra_pays_full_fee_canonical() { // Ultra: no subsidy except Read assert_eq!(subsidy_basis_points(HardwareTier::Ultra, ChainAction::Write), 0); assert_eq!(subsidy_basis_points(HardwareTier::Ultra, ChainAction::Compute), 0); assert_eq!(subsidy_basis_points(HardwareTier::Ultra, ChainAction::Transfer), 0); } #[test] fn ocafb_3_embedded_arm_subsidy_high_inmutable_16_canonical() { // Pi 5 user paga ~10% del Ultra fee (anti-clasista) assert_eq!( subsidy_basis_points(HardwareTier::EmbeddedArm, ChainAction::Write), 9000 ); assert_eq!( subsidy_basis_points(HardwareTier::EmbeddedArm, ChainAction::Compute), 10000 ); } // ─── OCAFB-4 entitlement matrix ─────────────────────────────── #[test] fn ocafb_4_free_compute_blocked_inmutable_37_canonical() { // Free Compute action blocked — upgrade required assert!(!entitlement_unlocked(SubscriptionTier::Free, ChainAction::Compute)); // Free Read/Write/Transfer baseline unlocked assert!(entitlement_unlocked(SubscriptionTier::Free, ChainAction::Read)); assert!(entitlement_unlocked(SubscriptionTier::Free, ChainAction::Write)); assert!(entitlement_unlocked(SubscriptionTier::Free, ChainAction::Transfer)); } #[test] fn ocafb_4_ultra_unlocks_all_inmutable_18_canonical() { // Ultra unlocks ALL actions incluyendo EEG Compute for action in [ ChainAction::Read, ChainAction::Write, ChainAction::Compute, ChainAction::Transfer, ] { assert!(entitlement_unlocked(SubscriptionTier::Ultra, action)); } } // ─── OCAFB-5 fee compute formula ────────────────────────────── #[test] fn ocafb_5_pi5_educational_world_free_canonical() { let mock = OitFeeScheduleMock::new() .with_hardware(HardwareTier::EmbeddedArm) .with_educational_world(true); assert!(mock.subsidy_eligible("alice", ChainAction::Write)); assert_eq!(mock.tier_multiplier("alice", ChainAction::Write), 0); } #[test] fn ocafb_5_ultra_pro_compute_full_fee_canonical() { let fee = compute_fee( 1000, HardwareTier::Ultra, SubscriptionTier::Pro, ChainAction::Compute, ); // base 1000 × hw_mult 10000 × sub_mult 15000 / 10000 / 10000 = 1500 assert_eq!(fee, 1500); } #[test] fn ocafb_5_free_user_compute_blocked_u64max_canonical() { // Free user Compute blocked — return u64::MAX let fee = compute_fee( 100, HardwareTier::Desktop, SubscriptionTier::Free, ChainAction::Compute, ); assert_eq!(fee, u64::MAX); } #[test] fn ocafb_5_short_circuit_full_subsidy_canonical() { // basis_points >= 10000 → fee=0 (Inmutable #16 strong override step 1) let fee = compute_fee( 999999, HardwareTier::EmbeddedArm, // Compute → 10000 subsidy SubscriptionTier::Ultra, // even Ultra ChainAction::Compute, ); assert_eq!(fee, 0); } #[test] fn ocafb_5_subsidy_override_educational_david_canon_canonical() { let mock = OitFeeScheduleMock::new() .with_hardware(HardwareTier::EmbeddedArm) .with_subscription(SubscriptionTier::Ultra) .with_educational_world(true); // Aún con Ultra subscription, educational_world override → free assert!(mock.subsidy_eligible("rural-student", ChainAction::Write)); assert!(mock.subsidy_eligible("rural-student", ChainAction::Compute)); } // ─── OitFeeScheduleMock builder pattern ────────────────────── #[test] fn mock_default_returns_free_embedded_arm_canonical() { let mock = OitFeeScheduleMock::new(); assert_eq!(mock.current_subscription_tier("anyone"), SubscriptionTier::Free); assert_eq!(mock.current_hardware_tier("anyone"), HardwareTier::EmbeddedArm); } #[test] fn mock_builder_with_overrides_canonical() { let mock = OitFeeScheduleMock::new() .with_subscription(SubscriptionTier::Ultra) .with_hardware(HardwareTier::Desktop); assert_eq!(mock.current_subscription_tier("user"), SubscriptionTier::Ultra); assert_eq!(mock.current_hardware_tier("user"), HardwareTier::Desktop); } #[test] fn arc_mock_dyn_compatible_canonical() { let mock = OitFeeScheduleMock::new(); let arc: Arc = arc_mock(mock); assert_eq!(arc.current_subscription_tier("anyone"), SubscriptionTier::Free); } // ─── Lobo blanco invariants compile-detect canonical ───────── /// Lobo blanco OCAFB-1: `OitFeeSchedule` trait MUST be dyn-compatible /// para que `pallet-origin-fees` (Charon Phase 2.4) pueda hold /// `Arc` cross-thread. #[test] fn oit_fee_schedule_trait_object_safe_lobo_blanco_canonical() { let _schedule: alloc::boxed::Box = alloc::boxed::Box::new(OitFeeScheduleMock::new()); } /// Lobo blanco OCAFB-1: `OitFeeSchedule` bounds `Send + Sync` para /// substrate runtime cross-pillar threading. #[test] fn oit_fee_schedule_send_sync_lobo_blanco_canonical() { fn assert_send_sync() {} assert_send_sync::(); } /// Lobo blanco OCAFB-3 Inmutable #16: subsidy matrix MUST guarantee /// EmbeddedArm pays strictly LESS than Ultra for non-Read actions /// (anti-clasista verification compile-detect). #[test] fn inmutable_16_embedded_arm_pays_less_than_ultra_lobo_blanco_canonical() { for action in [ChainAction::Write, ChainAction::Compute, ChainAction::Transfer] { let pi5_subsidy = subsidy_basis_points(HardwareTier::EmbeddedArm, action); let ultra_subsidy = subsidy_basis_points(HardwareTier::Ultra, action); assert!( pi5_subsidy > ultra_subsidy, "Lobo blanco Inmutable #16 anti-clasista violated for {action:?}: \ Pi5 subsidy {pi5_subsidy} bp must be > Ultra subsidy {ultra_subsidy} bp" ); } } }