Files
arkhe-origin-chain/crates/origin-chain-fees/src/fee_schedule.rs
T
davidherranandClaude Opus 4.8 ca50938495 feat(tiers): EmbeddedAI on-chain — base_multiplier + subsidy piso EmbeddedArm (#16)
Espejea HardwareTier::EmbeddedAI (Jetson Orin Nano) en el mirror de la cadena,
en lockstep con arkhe-core bbac1b2 (Ergon Fase 1 EmbeddedAI, ADR-0072 / DC 2ba235e1).

Trampa de la fuente preservada byte-fiel: EmbeddedAI APPEND discriminante 4
(SCALE wire-safe, attestations runtime_tier:u8 viejas válidas) PERO rank de
capacidad 1 (entre EmbeddedArm=0 y Portable=2). Por eso Ord es MANUAL por rank()
(no derive — un derive pondría EmbeddedAI > Ultra, falso) y el gating Hero es por
rank, no por discriminante.

- tiers.rs: variant EmbeddedAI (last) + Ord/PartialOrd manual por rank() +
  rank()/ordinal()/from_ordinal() SSOT (el reverse-map que evita perder el
  tier-floor al deserializar, #16) + base_multiplier_x100=10 (piso, el estudiante
  en Jetson no paga más que en Pi 5) + surcharge Hero gated=0 + supports_hero=false.
- fee_schedule.rs: 6 arms EmbeddedAI = piso EmbeddedArm en subsidy_basis_points +
  educational override incluye EmbeddedAI (device de estudiante).
- fees.rs / unified.rs: cobertura — compute_fee(EmbeddedAI)==EmbeddedArm + grid de
  paridad OCAFB-5 extendido a EmbeddedAI.

Tests: 21 suites / 261 workspace verde, 0 regresión. SCALE index == ordinal sellado.
FIX-4 (verificador attestation) ortogonal — no mapea el enum, intacto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:24:17 -05:00

501 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # 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<dyn OitFeeSchedule>`.
///
/// **`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<dyn OitFeeSchedule>` 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<dyn OitFeeSchedule>` 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<dyn OitFeeSchedule>`.
pub fn arc_mock(mock: OitFeeScheduleMock) -> Arc<dyn OitFeeSchedule> {
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<dyn OitFeeSchedule> = 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<dyn OitFeeSchedule>` cross-thread.
#[test]
fn oit_fee_schedule_trait_object_safe_lobo_blanco_canonical() {
let _schedule: alloc::boxed::Box<dyn OitFeeSchedule> = 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<T: Send + Sync>() {}
assert_send_sync::<OitFeeScheduleMock>();
}
/// 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"
);
}
}
}