From 8e724969013a6d13114fd0796845b95db9818695 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Sun, 21 Jun 2026 21:01:38 -0500 Subject: [PATCH] =?UTF-8?q?feat(fees,genesis):=20FeesApi=20account-aligned?= =?UTF-8?q?=20query=20+=20250M=20Foundation=E2=86=92vault=20og/gvtry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dos endurecimientos pre-mainnet (leftovers del remediation roadmap): 1. genesis vault (#16/CHN-4): el preset mainnet asigna el 25% Foundation Treasury (250M OGC) al VAULT de governance (og/gvtry, GovernanceTreasuryPalletId), NO a una cuenta key-controlada. Es la MISMA cuenta de la que treasury_disbursement gasta (gated por governance council) y que emission/staking alimentan → el treasury queda DAO-governed desde el bloque 0, materializando "Foundation Treasury → DAO governed (CHN-4)". El placeholder FNDT queda SOLO como sudo_key interim hasta el sunset (DC ORIGIN_CHAIN_GOVERNANCE_SUNSET_V1). Las allocations BM V2 (40/25/15/15/5 = 1B) no cambian de monto. 2. FeesApi alignment: nuevo método query_fee_for_account(action, base, account) que estima el fee con el TierProfile REAL on-chain del account (storage TierProfiles, ValueQuery default Desktop+Free), NO caller-supplied — alineado con el fee-path vivo OriginFeeAdapter (FIX-6 Stage 2a). Cierra el "el cliente adivinaba hw/sub": la estimación off-chain ahora usa la misma fórmula sobre el MISMO tier que cobra la cadena. Método aditivo al Runtime API ⇒ TRANSACTION_VERSION 1 intacto. RUNTIME_VERSION 4→5 (nueva metadata de FeesApi). 268 workspace tests verde (native, SKIP_WASM_BUILD — WASM VPS-gated), 0 regresión. Test sella que query_fee_for_account usa el perfil real (EmbeddedArm piso < default Desktop). Co-Authored-By: Claude Opus 4.8 --- crates/origin-chain-fees/src/lib.rs | 23 ++++++++++++ crates/origin-chain-fees/src/tests_adapter.rs | 37 +++++++++++++++++++ .../src/genesis_config_presets.rs | 22 ++++++++--- crates/origin-chain-runtime/src/lib.rs | 26 ++++++++++--- 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/crates/origin-chain-fees/src/lib.rs b/crates/origin-chain-fees/src/lib.rs index 1f759e0..76ab1e3 100644 --- a/crates/origin-chain-fees/src/lib.rs +++ b/crates/origin-chain-fees/src/lib.rs @@ -99,6 +99,19 @@ sp_api::decl_runtime_apis! { hw: origin_chain_types::HardwareTier, sub: origin_chain_types::SubscriptionTier, ) -> u64; + + /// Query fee canonical para `(action, base)` usando el `TierProfile` + /// REAL on-chain del `account` (storage `TierProfiles`, NUNCA + /// caller-supplied — alineado con el fee-path vivo `OriginFeeAdapter`). + /// Un account sin perfil asignado usa el default conservador + /// (`Desktop + Free`, ValueQuery). Read-only canonical (off-chain RPC). + /// Cierra el gap "el cliente adivinaba hw/sub": ahora la estimación es + /// la misma fórmula sobre el MISMO tier que cobra la cadena. + fn query_fee_for_account( + action: origin_chain_types::ChainAction, + base: u64, + account: origin_chain_types::AccountId, + ) -> u64; } } @@ -350,6 +363,16 @@ pub mod pallet { crate::compute_fee(base, hw, sub, action) } + /// Igual que [`Self::query_fee`] pero lee el `TierProfile` REAL on-chain + /// del `account` (storage `TierProfiles`, ValueQuery → default conservador + /// `Desktop + Free` si no hay perfil). Alinea la estimación off-chain con + /// el tier que el `OriginFeeAdapter` realmente cobra (FIX-6 Stage 2a) — + /// nunca caller-supplied, cierra el tier-guessing del cliente. + pub fn query_fee_for_account(action: ChainAction, base: u64, account: &T::AccountId) -> u64 { + let profile = TierProfiles::::get(account); + crate::compute_fee(base, profile.hw, profile.sub, action) + } + /// FIX-6 Stage 2a: cuenta del subsidy pool `og/subsy` (determinística, /// keyless — derivada del MISMO PalletId que alimenta la emisión). pub fn subsidy_account() -> T::AccountId { diff --git a/crates/origin-chain-fees/src/tests_adapter.rs b/crates/origin-chain-fees/src/tests_adapter.rs index e75a041..4a97942 100644 --- a/crates/origin-chain-fees/src/tests_adapter.rs +++ b/crates/origin-chain-fees/src/tests_adapter.rs @@ -104,6 +104,43 @@ fn default_profile_es_desktop_free_conservador() { }); } +#[test] +fn query_fee_for_account_usa_el_tier_profile_real_on_chain() { + new_test_ext().execute_with(|| { + let base = 1_000u64; + let action = origin_chain_types::ChainAction::Write; + + // Sin perfil asignado → default conservador Desktop+Free (igual que + // query_fee con Desktop+Free, NO caller-supplied). + let default_fee = + Fees::query_fee(action, base, HardwareTier::Desktop, SubscriptionTier::Free); + assert_eq!( + Fees::query_fee_for_account(action, base, &alice_account()), + default_fee, + "account sin perfil usa el default Desktop+Free" + ); + + // Con perfil EmbeddedArm+Free → query_fee_for_account refleja el piso #16 + // (subsidy alta), distinto del default Desktop. + set_profile( + alice_account(), + HardwareTier::EmbeddedArm, + SubscriptionTier::Free, + ); + let arm_fee = + Fees::query_fee(action, base, HardwareTier::EmbeddedArm, SubscriptionTier::Free); + assert_eq!( + Fees::query_fee_for_account(action, base, &alice_account()), + arm_fee, + "account con perfil EmbeddedArm usa ese tier real" + ); + assert!( + arm_fee < default_fee, + "EmbeddedArm (piso #16) paga menos que el default Desktop" + ); + }); +} + #[test] fn set_tier_profile_es_root_only() { new_test_ext().execute_with(|| { diff --git a/crates/origin-chain-runtime/src/genesis_config_presets.rs b/crates/origin-chain-runtime/src/genesis_config_presets.rs index 70a8252..97ef63d 100644 --- a/crates/origin-chain-runtime/src/genesis_config_presets.rs +++ b/crates/origin-chain-runtime/src/genesis_config_presets.rs @@ -39,7 +39,7 @@ #![cfg(feature = "std")] use crate::{ - configs::{AuraId, GrandpaId}, + configs::{AuraId, GovernanceTreasuryPalletId, GrandpaId}, opaque::SessionKeys, Balance, ChainAccountId, Ogc, RuntimeGenesisConfig, }; @@ -47,6 +47,7 @@ use alloc::vec; use alloc::vec::Vec; use origin_chain_types::{HardwareTier, SubscriptionTier}; use sp_genesis_builder::PresetId; +use sp_runtime::traits::AccountIdConversion; // ============================================================ // Canonical constants para mainnet allocation (BM V2) @@ -343,11 +344,19 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig { }; let ecosystem_pool = make_placeholder(b"ECO!"); // Ecosystem/Rewards Pool canonical - let foundation_treasury = make_placeholder(b"FNDT"); // Foundation Treasury canonical + let foundation_treasury = make_placeholder(b"FNDT"); // Foundation multisig = sudo_key interim let public_sale = make_placeholder(b"SALE"); // Public Sale canonical let founding_team = make_placeholder(b"TEAM"); // Founding Team canonical (4yr vesting) let exchange_liquidity = make_placeholder(b"LIQD"); // Exchange Liquidity canonical + // El 25% Foundation Treasury (250M) se asigna al VAULT de governance (og/gvtry), + // NO a una cuenta key-controlada — materializa "DAO governed (CHN-4)" desde el + // bloque 0. Es la MISMA cuenta de la que `treasury_disbursement` gasta (gated por + // governance council) y que emission/staking alimentan. El placeholder FNDT queda + // SOLO como sudo_key interim (Root) hasta el sunset a governance pre-mainnet. + let governance_vault: ChainAccountId = + GovernanceTreasuryPalletId::get().into_account_truncating(); + // 5 initial validators canonical Pi 5 placeholders (Dream House Studios + universities). let validators = vec![ placeholder_mainnet_validator(0), @@ -361,15 +370,16 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig { // BM V2 allocation canonical exact: 40/25/15/15/5 → 1B OGC total vec![ (ecosystem_pool, ECOSYSTEM_ALLOCATION), - (foundation_treasury.clone(), FOUNDATION_ALLOCATION), + (governance_vault, FOUNDATION_ALLOCATION), // 250M → vault og/gvtry (DAO-governed) (public_sale, PUBLIC_SALE_ALLOCATION), (founding_team, FOUNDING_TEAM_ALLOCATION), (exchange_liquidity, EXCHANGE_LIQUIDITY_ALLOCATION), ], validators, - // FIX-5: Foundation Treasury = sudo key interim (Root) en mainnet - // (multisig humano). PRE-LAUNCH Q1 2027: David ratifica la key real + - // sunset a governance (pallet-collective/referenda). + // FIX-5: el placeholder FNDT (Foundation multisig humano) = sudo key interim + // (Root) en mainnet. PRE-LAUNCH Q1 2027: David ratifica la key real + sunset + // a governance (pallet-collective/referenda). El treasury (250M) ya NO vive + // aquí — está en el vault og/gvtry, gastable solo vía governance. foundation_treasury, // FIX-6 Stage 2a: mainnet arranca SIN perfiles sembrados — todos en el // Default conservador (Desktop+Free) hasta que la attestation real diff --git a/crates/origin-chain-runtime/src/lib.rs b/crates/origin-chain-runtime/src/lib.rs index e1b7d75..209e262 100644 --- a/crates/origin-chain-runtime/src/lib.rs +++ b/crates/origin-chain-runtime/src/lib.rs @@ -233,7 +233,10 @@ pub const IMPL_NAME: &str = "origin-chain-charon"; /// en pallet-origin-emission Index 13: nuevo call ⇒ cambia metadata. El call es /// aditivo en un call_index nuevo — TRANSACTION_VERSION intacto). Palanca Root /// para pausar inflación + kill-switch anti-doble-emisión para la activación NPoS. -pub const RUNTIME_VERSION: u32 = 4; +/// v5: FeesApi alignment — nuevo método `query_fee_for_account` (estima el fee +/// con el TierProfile REAL on-chain del account, no caller-supplied). Cambia la +/// metadata del Runtime API FeesApi; método aditivo ⇒ TRANSACTION_VERSION intacto. +pub const RUNTIME_VERSION: u32 = 5; /// Canonical authoring version per Substrate semver canonical. pub const AUTHORING_VERSION: u32 = 1; @@ -660,6 +663,17 @@ impl_runtime_apis! { ) -> u64 { Fees::query_fee(action, base, hw, sub) } + + fn query_fee_for_account( + action: origin_chain_types::ChainAction, + base: u64, + account: origin_chain_types::AccountId, + ) -> u64 { + // account es el newtype origin_chain_types::AccountId; el storage key + // canonical es ChainAccountId = AccountId32 (32-byte, bytes-level). + let runtime_account = ChainAccountId::from(account.0); + Fees::query_fee_for_account(action, base, &runtime_account) + } } // ============================================================ @@ -814,11 +828,11 @@ mod tests { use super::*; #[test] - fn runtime_version_v4_emission_kill_switch() { - // v4 = FIX-6 Stage 2b governance emission kill-switch (nuevo call en - // Emission Index 13). El formato de extrinsic de los calls EXISTENTES NO - // cambió (call aditivo en call_index nuevo) ⇒ TRANSACTION_VERSION intacto. - assert_eq!(RUNTIME_VERSION, 4); + fn runtime_version_v5_fees_api_account_alignment() { + // v5 = FeesApi.query_fee_for_account (estima con el TierProfile real + // on-chain). Método aditivo al Runtime API ⇒ los calls/extrinsics + // EXISTENTES no cambian su formato ⇒ TRANSACTION_VERSION intacto. + assert_eq!(RUNTIME_VERSION, 5); assert_eq!(TRANSACTION_VERSION, 1); }