feat(fees,genesis): FeesApi account-aligned query + 250M Foundation→vault og/gvtry
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
112747ab44
commit
8e72496901
4 files changed
+96
-12
No files matched your search
@@ -99,6 +99,19 @@ sp_api::decl_runtime_apis! {
|
|||||||
hw: origin_chain_types::HardwareTier,
|
hw: origin_chain_types::HardwareTier,
|
||||||
sub: origin_chain_types::SubscriptionTier,
|
sub: origin_chain_types::SubscriptionTier,
|
||||||
) -> u64;
|
) -> 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)
|
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::<T>::get(account);
|
||||||
|
crate::compute_fee(base, profile.hw, profile.sub, action)
|
||||||
|
}
|
||||||
|
|
||||||
/// FIX-6 Stage 2a: cuenta del subsidy pool `og/subsy` (determinística,
|
/// FIX-6 Stage 2a: cuenta del subsidy pool `og/subsy` (determinística,
|
||||||
/// keyless — derivada del MISMO PalletId que alimenta la emisión).
|
/// keyless — derivada del MISMO PalletId que alimenta la emisión).
|
||||||
pub fn subsidy_account() -> T::AccountId {
|
pub fn subsidy_account() -> T::AccountId {
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn set_tier_profile_es_root_only() {
|
fn set_tier_profile_es_root_only() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
#![cfg(feature = "std")]
|
#![cfg(feature = "std")]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::{AuraId, GrandpaId},
|
configs::{AuraId, GovernanceTreasuryPalletId, GrandpaId},
|
||||||
opaque::SessionKeys,
|
opaque::SessionKeys,
|
||||||
Balance, ChainAccountId, Ogc, RuntimeGenesisConfig,
|
Balance, ChainAccountId, Ogc, RuntimeGenesisConfig,
|
||||||
};
|
};
|
||||||
@@ -47,6 +47,7 @@ use alloc::vec;
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use origin_chain_types::{HardwareTier, SubscriptionTier};
|
use origin_chain_types::{HardwareTier, SubscriptionTier};
|
||||||
use sp_genesis_builder::PresetId;
|
use sp_genesis_builder::PresetId;
|
||||||
|
use sp_runtime::traits::AccountIdConversion;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Canonical constants para mainnet allocation (BM V2)
|
// 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 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 public_sale = make_placeholder(b"SALE"); // Public Sale canonical
|
||||||
let founding_team = make_placeholder(b"TEAM"); // Founding Team canonical (4yr vesting)
|
let founding_team = make_placeholder(b"TEAM"); // Founding Team canonical (4yr vesting)
|
||||||
let exchange_liquidity = make_placeholder(b"LIQD"); // Exchange Liquidity canonical
|
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).
|
// 5 initial validators canonical Pi 5 placeholders (Dream House Studios + universities).
|
||||||
let validators = vec![
|
let validators = vec![
|
||||||
placeholder_mainnet_validator(0),
|
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
|
// BM V2 allocation canonical exact: 40/25/15/15/5 → 1B OGC total
|
||||||
vec![
|
vec![
|
||||||
(ecosystem_pool, ECOSYSTEM_ALLOCATION),
|
(ecosystem_pool, ECOSYSTEM_ALLOCATION),
|
||||||
(foundation_treasury.clone(), FOUNDATION_ALLOCATION),
|
(governance_vault, FOUNDATION_ALLOCATION), // 250M → vault og/gvtry (DAO-governed)
|
||||||
(public_sale, PUBLIC_SALE_ALLOCATION),
|
(public_sale, PUBLIC_SALE_ALLOCATION),
|
||||||
(founding_team, FOUNDING_TEAM_ALLOCATION),
|
(founding_team, FOUNDING_TEAM_ALLOCATION),
|
||||||
(exchange_liquidity, EXCHANGE_LIQUIDITY_ALLOCATION),
|
(exchange_liquidity, EXCHANGE_LIQUIDITY_ALLOCATION),
|
||||||
],
|
],
|
||||||
validators,
|
validators,
|
||||||
// FIX-5: Foundation Treasury = sudo key interim (Root) en mainnet
|
// FIX-5: el placeholder FNDT (Foundation multisig humano) = sudo key interim
|
||||||
// (multisig humano). PRE-LAUNCH Q1 2027: David ratifica la key real +
|
// (Root) en mainnet. PRE-LAUNCH Q1 2027: David ratifica la key real + sunset
|
||||||
// sunset a governance (pallet-collective/referenda).
|
// 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,
|
foundation_treasury,
|
||||||
// FIX-6 Stage 2a: mainnet arranca SIN perfiles sembrados — todos en el
|
// FIX-6 Stage 2a: mainnet arranca SIN perfiles sembrados — todos en el
|
||||||
// Default conservador (Desktop+Free) hasta que la attestation real
|
// Default conservador (Desktop+Free) hasta que la attestation real
|
||||||
|
|||||||
@@ -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
|
/// en pallet-origin-emission Index 13: nuevo call ⇒ cambia metadata. El call es
|
||||||
/// aditivo en un call_index nuevo — TRANSACTION_VERSION intacto). Palanca Root
|
/// 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.
|
/// 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.
|
/// Canonical authoring version per Substrate semver canonical.
|
||||||
pub const AUTHORING_VERSION: u32 = 1;
|
pub const AUTHORING_VERSION: u32 = 1;
|
||||||
@@ -660,6 +663,17 @@ impl_runtime_apis! {
|
|||||||
) -> u64 {
|
) -> u64 {
|
||||||
Fees::query_fee(action, base, hw, sub)
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_version_v4_emission_kill_switch() {
|
fn runtime_version_v5_fees_api_account_alignment() {
|
||||||
// v4 = FIX-6 Stage 2b governance emission kill-switch (nuevo call en
|
// v5 = FeesApi.query_fee_for_account (estima con el TierProfile real
|
||||||
// Emission Index 13). El formato de extrinsic de los calls EXISTENTES NO
|
// on-chain). Método aditivo al Runtime API ⇒ los calls/extrinsics
|
||||||
// cambió (call aditivo en call_index nuevo) ⇒ TRANSACTION_VERSION intacto.
|
// EXISTENTES no cambian su formato ⇒ TRANSACTION_VERSION intacto.
|
||||||
assert_eq!(RUNTIME_VERSION, 4);
|
assert_eq!(RUNTIME_VERSION, 5);
|
||||||
assert_eq!(TRANSACTION_VERSION, 1);
|
assert_eq!(TRANSACTION_VERSION, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user