feat(fees): v0.0.22 — compute_fee_for_action impl complete + mock runtime integration tests E2E (10 scenarios)
Phase 2.4 FEES pallet REAL IMPL canonical. Dispatchable compute_fee_for_action wire-up vendor compute_fee() pure fn call + AccountFees<StorageMap> saturating arithmetic + event emission. Mock runtime + 10 integration tests E2E pattern v0.0.18 mirror (Phase 2.1 PROVENANCE). ## Real impl canonical 5-step OCAFB-5 1. ensure_signed canonical 2. Vendor compute_fee(base, hw, sub, action) pure fn call 3. Handle entitlement blocked (fee==u64::MAX → Err(EntitlementLocked)) 4. AccountFees<StorageMap> saturating update canonical 5. Emit FeeCharged event canonical Pure helper Pallet::<T>::query_fee() para Runtime API v0.0.23 consumer. ## Consumer pattern canonical Mathi OitFeeSchedule consumed CLIENT-SIDE — tx submitter queries Mathi per-user → assembles tx con (hw, sub) baked → submits. Pattern análogo Phase 2.1 attest_asset (ValidationAttestation pre-assembled client-side). ## 10 integration tests E2E - scenario_1: Pi5 Write subsidy 90% → fee=1000 (Inmutable #16) - scenario_2: Ultra Write Pro multiplier → fee=1500 - scenario_3: Read always free → fee=0 (Inmutable #37) - scenario_4: Free Compute blocked → Err(EntitlementLocked) - scenario_5: Pro Compute unlocked → fee=112 - scenario_6: Ultra Compute Ultra premium → fee=2000 (Inmutable #18) - scenario_7: Storage accumulates Write×3 → total_paid=3375, count=3 - scenario_8: ChainAction ordinal stable wire (Read=0..Transfer=3) - scenario_9: Inmutable #16 lobo blanco — Pi5 fee 1000 < Ultra fee 20000 - scenario_10: 5-step OCAFB-5 walkthrough explicit Pre/Post assertions ## FRAME canonical pattern Events NOT emitted on Err path (FRAME revierte events on dispatch error). EntitlementBlocked event variant retained para Phase 2.5 future with_storage_layer pattern. Caller infers from Err return. ## Files canonical - EDIT: crates/origin-chain-fees/src/lib.rs (real dispatchable impl + query_fee helper) - NEW: crates/origin-chain-fees/src/mock.rs (80 LOC mock runtime pattern v0.0.18) - NEW: crates/origin-chain-fees/src/tests_integration.rs (290 LOC, 10 tests) - EDIT: crates/origin-chain-types/src/tiers.rs (FRAME derives Encode/Decode/TypeInfo/MaxEncodedLen para HardwareTier + SubscriptionTier + AssetQualityTier) - EDIT: CHANGELOG.md (v0.0.22 entry comprehensive) ## Verification - cargo test -p origin-chain-fees — 32 tests verde, 0 failures - cargo test --workspace — 137 tests verde workspace, 0 failures - Zero compile errors workspace-wide 🛠️ Phase 2.4 real impl canonical. Runtime API v0.0.23 next ship. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0a83395a16
commit
e935233ba6
5 files changed
+570
-34
No files matched your search
@@ -0,0 +1,303 @@
|
||||
//! # 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::<Test>::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::<Test>::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::<Test>::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::<Test>::EntitlementLocked
|
||||
);
|
||||
|
||||
// Storage NOT updated (entitlement blocked early)
|
||||
assert!(AccountFees::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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<StorageMap> 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::<Test>::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,
|
||||
}));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user