//! # Integration tests canonical v0.0.22 — Mock runtime E2E OCAFB-5 flow //! //! 10 scenarios canonical compute_fee_for_action flow end-to-end via mock runtime: //! //! | Scenario | HW | Sub | Action | Expected outcome | //! |----------|----|----|--------|------------------| //! | scenario_1_pi5_write_subsidy_90 | EmbeddedArm | Free | Write | fee=10% base (subsidy 9000 bp) | //! | scenario_2_ultra_write_full_price | Ultra | Pro | Write | fee=base × 1.5 multiplier | //! | scenario_3_read_always_free_inmutable_37 | Desktop | Free | Read | fee=0 (subsidy 100%) | //! | scenario_4_free_compute_blocked_entitlement | Desktop | Free | Compute | Error::EntitlementLocked + event | //! | scenario_5_pro_compute_unlocked | Desktop | Pro | Compute | fee normal | //! | scenario_6_ultra_compute_premium | Ultra | Ultra | Compute | fee=base × 2.0 multiplier | //! | scenario_7_storage_accumulates | Desktop | Pro | Write × 3 | history.total_paid sum + count=3 | //! | scenario_8_chain_action_ordinal_stable_wire | wire format invariant | n/a | Read=0 + Write=1 + Compute=2 + Transfer=3 | //! | scenario_9_inmutable_16_anti_clasista_lobo_blanco | Pi5 < Ultra fee non-Read | n/a | compile-detect invariant | //! | scenario_10_5_step_walkthrough | EmbeddedArm | Free | Transfer | explicit Pre/Post-flight assertions | //! //! Total: 10 integration tests canonical E2E. Pattern v0.0.18 mirror. #![cfg(test)] use crate::mock::{alice_account, alice_origin, bob_account, bob_origin, new_test_ext, Fees, RuntimeEvent, System, Test}; use crate::{AccountFees, Error, Event}; use frame_support::{assert_noop, assert_ok}; use origin_chain_types::{ChainAction, HardwareTier, SubscriptionTier}; // ─── Scenario 1: Pi 5 Write subsidy 90% canonical (Inmutable #16) ──── #[test] fn scenario_1_pi5_write_subsidy_90_canonical() { new_test_ext().execute_with(|| { let base = 10_000u64; assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Write, base, HardwareTier::EmbeddedArm, SubscriptionTier::Free, )); // OCAFB-5 formula: base 10000 × hw_mult 1000 × sub_mult 10000 / 10000 / 10000 = 1000 // (subsidy 9000 bp → remaining 1000 bp × Free multiplier 10000 bp) let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 1000u128); // Pi 5 paga 10% del base — Inmutable #16 assert_eq!(history.action_count, 1); System::assert_last_event(RuntimeEvent::Fees(Event::FeeCharged { who: alice_account(), action: ChainAction::Write, fee_charged: 1000, })); }); } // ─── Scenario 2: Ultra Write Pro multiplier canonical ─────────────── #[test] fn scenario_2_ultra_write_full_price_canonical() { new_test_ext().execute_with(|| { let base = 1_000u64; assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Write, base, HardwareTier::Ultra, SubscriptionTier::Pro, )); // OCAFB-5: base 1000 × hw_mult 10000 (Ultra subsidy=0 → full remaining) × sub_mult 15000 / 10000 / 10000 = 1500 let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 1500u128); assert_eq!(history.action_count, 1); }); } // ─── Scenario 3: Read always free canonical (Inmutable #37) ────────── #[test] fn scenario_3_read_always_free_inmutable_37_canonical() { new_test_ext().execute_with(|| { assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Read, 999_999, HardwareTier::Desktop, // even Desktop SubscriptionTier::Free, )); // OCAFB-3: Read always 100% subsidy → fee=0 (Inmutable #37 baseline) let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 0u128); assert_eq!(history.action_count, 1); // count incremented even si fee=0 }); } // ─── Scenario 4: Free Compute blocked entitlement canonical (Inmutable #37) ── #[test] fn scenario_4_free_compute_blocked_entitlement_canonical() { new_test_ext().execute_with(|| { // OCAFB-4 entitlement enforce: Free + Compute → blocked // FRAME canonical: dispatchable returns `Err(EntitlementLocked)` SIN event // emission (events revierten on Err per FRAME dispatch transaction). // `EntitlementBlocked` event variant reserved para Phase 2.5 future // `with_storage_layer` pattern. assert_noop!( Fees::compute_fee_for_action( alice_origin(), ChainAction::Compute, 100, HardwareTier::Desktop, SubscriptionTier::Free, // Compute blocked per OCAFB-4 ), Error::::EntitlementLocked ); // Storage NOT updated (entitlement blocked early) assert!(AccountFees::::get(&alice_account()).is_none()); // Caller infers blocked entitlement from `Err(EntitlementLocked)` return, // no event canonical (FRAME revierte events on Err scaffold v0.0.22). }); } // ─── Scenario 5: Pro Compute unlocked canonical ───────────────────── #[test] fn scenario_5_pro_compute_unlocked_canonical() { new_test_ext().execute_with(|| { let base = 100u64; assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Compute, base, HardwareTier::Desktop, SubscriptionTier::Pro, )); // OCAFB-5: base 100 × hw_mult 7500 (Desktop Compute subsidy 2500 → 7500 remaining) × sub_mult 15000 / 10000 / 10000 = 112 let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 112u128); // floor division round-down (112.5 → 112) assert_eq!(history.action_count, 1); }); } // ─── Scenario 6: Ultra Compute premium canonical (Inmutable #18) ──── #[test] fn scenario_6_ultra_compute_premium_canonical() { new_test_ext().execute_with(|| { let base = 1_000u64; assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Compute, base, HardwareTier::Ultra, SubscriptionTier::Ultra, // EEG Compute Ultra-only (Inmutable #18) )); // OCAFB-5: base 1000 × hw_mult 10000 (Ultra subsidy=0) × sub_mult 20000 / 10000 / 10000 = 2000 let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 2000u128); assert_eq!(history.action_count, 1); }); } // ─── Scenario 7: Storage accumulates over multiple writes canonical ── #[test] fn scenario_7_storage_accumulates_canonical() { new_test_ext().execute_with(|| { let base = 1_000u64; // 3 writes secuenciales for _ in 0..3 { assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Write, base, HardwareTier::Desktop, SubscriptionTier::Pro, )); } // OCAFB-5: base 1000 × hw_mult 7500 × sub_mult 15000 / 10000 / 10000 = 1125 per write // After 3 writes: total_paid = 3 × 1125 = 3375, action_count = 3 let history = AccountFees::::get(&alice_account()).expect("storage updated"); assert_eq!(history.total_paid, 3375u128); assert_eq!(history.action_count, 3); }); } // ─── Scenario 8: ChainAction ordinal stable wire format canonical ──── #[test] fn scenario_8_chain_action_ordinal_stable_wire_canonical() { // Wire format invariant — sin dependency mock runtime assert_eq!(ChainAction::Read.ordinal(), 0); assert_eq!(ChainAction::Write.ordinal(), 1); assert_eq!(ChainAction::Compute.ordinal(), 2); assert_eq!(ChainAction::Transfer.ordinal(), 3); // Roundtrip canonical for action in [ChainAction::Read, ChainAction::Write, ChainAction::Compute, ChainAction::Transfer] { assert_eq!(ChainAction::from_ordinal(action.ordinal()), Some(action)); } } // ─── Scenario 9: Inmutable #16 anti-clasista lobo blanco canonical ─── #[test] fn scenario_9_inmutable_16_anti_clasista_lobo_blanco_canonical() { new_test_ext().execute_with(|| { let base = 10_000u64; // Pi 5 user (EmbeddedArm + Free) assert_ok!(Fees::compute_fee_for_action( alice_origin(), ChainAction::Write, base, HardwareTier::EmbeddedArm, SubscriptionTier::Free, )); let pi5_fee = AccountFees::::get(&alice_account()).unwrap().total_paid; // Ultra user (Ultra + Ultra) assert_ok!(Fees::compute_fee_for_action( bob_origin(), ChainAction::Write, base, HardwareTier::Ultra, SubscriptionTier::Ultra, )); let ultra_fee = AccountFees::::get(&bob_account()).unwrap().total_paid; // Lobo blanco compile-detect canonical: Pi 5 paga ESTRICTAMENTE MENOS que Ultra assert!( pi5_fee < ultra_fee, "Inmutable #16 anti-clasista violated: Pi 5 fee {pi5_fee} debe ser < Ultra fee {ultra_fee}" ); // Ratio canonical aprox 1:20 (Pi 5 1000 vs Ultra 20000) // Pi 5: 10000 × 1000 × 10000 / 10000 / 10000 = 1000 // Ultra: 10000 × 10000 × 20000 / 10000 / 10000 = 20000 assert_eq!(pi5_fee, 1000u128); assert_eq!(ultra_fee, 20000u128); }); } // ─── Scenario 10: 5-step OCAFB-5 walkthrough explicit canonical ────── #[test] fn scenario_10_5_step_walkthrough_canonical() { new_test_ext().execute_with(|| { let base = 1_000u64; let action = ChainAction::Transfer; let hw = HardwareTier::EmbeddedArm; let sub = SubscriptionTier::Free; // Pre-flight assertions canonical assert!( AccountFees::::get(&alice_account()).is_none(), "PRE-flight OCAFB-2: storage canonical empty" ); // Step 1: ensure_signed canonical (implicit via dispatchable) // Step 2: vendor compute_fee canonical OCAFB-5 (pure fn call) // Step 3: entitlement check (Transfer Free allowed, no block) // Step 4: AccountFees update saturating canonical // Step 5: emit FeeCharged event canonical assert_ok!(Fees::compute_fee_for_action( alice_origin(), action, base, hw, sub, )); // Post-flight assertions canonical let history = AccountFees::::get(&alice_account()) .expect("POST-flight OCAFB-2: AccountFees persisted canonical"); // OCAFB-5: base 1000 × hw_mult 1000 (EmbeddedArm Transfer subsidy 9000 → 1000 remaining) × sub_mult 10000 / 10000 / 10000 = 100 assert_eq!( history.total_paid, 100u128, "POST-flight: total_paid canonical Pi 5 anti-clasista" ); assert_eq!( history.action_count, 1, "POST-flight: action_count canonical incremented" ); System::assert_last_event(RuntimeEvent::Fees(Event::FeeCharged { who: alice_account(), action, fee_charged: 100, })); }); }