feat(fees): FIX-6 Stage 2a — fee unificado (cierra V2-CA3)
Pone la fórmula OCAFB-5 en el fee-path VIVO: OriginFeeAdapter (OnChargeTransaction custom) reemplaza al FungibleAdapter tier-blind. El firmante paga su fracción según (HardwareTier × ChainAction × SubscriptionTier, tiers leídos ON-CHAIN de TierProfiles — jamás caller-supplied), y el subsidy pool og/subsy (alimentado por la emisión Stage 1) cubre la fracción subsidiada. Inmutable #16 en cada transacción. OU=() = burn 100% gas fees (matriz CHN-2). unified_fee (u128, outcome tipado FeeOutcome — sin el sentinel u64::MAX in-band): user = fórmula vendor Mathi byte-fiel (test de paridad grid contra ce4fafb); pool = net × subsidy_bp/1e4 sobre el COSTO BASE (anti-regresivo, sub_mult ORTOGONAL: un Ultra NO drena el pool más que un Free — el premium lo paga el usuario, no el commons). Hallazgos de la verificación adversarial (workflow ultracode, 4 lentes) cerrados de raíz: - HIGH: clasificador cubre eth_instantiate_with_code (call_index 10) — evadía el entitlement-gate DeployContract vía fallback Write. - MEDIUM ledger envenenable: removido el dispatchable legacy compute_fee_for_action (tiers caller-supplied, escribía AccountFees sin cobrar). AccountFees ahora escrito SOLO por el fee-path vivo (note_settlement). Eventos que nunca se emitían eliminados; el preview read-only vive en query_fee / FeesApi. - MEDIUM drenaje del pool por sub_mult: pool sobre net, no sobre el precio premium (Opción B anti-regresiva). - Dev UX: GenesisConfig siembra perfiles iniciales; dev/local arrancan Ultra (los Slices 3.x de contracts no se bloquean por Free+DeployContract); mainnet vacío (Default conservador hasta attestation). Decisión de diseño (autoridad Pilar 4): replay de attestation = RECHAZO explícito, no dedup silencioso (notificado a Ergon para el reference impl). Orden OCAFB-5 subsidy-antes-de-entitlement preservado byte-fiel + notificado a Mathi para el ratify 2026-06-21. set_tier_profile Root-only interim (sunset a attestation-driven vía Mathi PerceptionAdapter + DID binding FIX-3/FIX-4). spec_version 1→2 (formato de extrinsic intacto, TRANSACTION_VERSION=1). Tests: 49 crate fees (paridad grid + lobo blanco ortogonalidad + refund + pool seco + tip passthrough + entitlement blocked) + 21 suites workspace verde. Toolchain cargo +1.88. Nota WASM: el build local del runtime WASM falla en la post-validación del substrate-wasm-builder con UnknownOpcode(252) — límite del toolchain (Rust 1.88 emite opcodes 0xFC que el parser del builder de stable2509 no deserializa). Reproducido idéntico en el HEAD intacto (v0.0.57): es ambiental, no de este cambio. El node real + WASM artifact siguen VPS-gated (toolchain alineado / wasm32v1-none cuando el wasm-builder lo soporte). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e6a803b54f
commit
3364994cce
10 files changed
+1435
-417
No files matched your search
@@ -159,13 +159,59 @@ parameter_types! {
|
||||
pub const OperationalFeeMultiplier: u8 = 5;
|
||||
}
|
||||
|
||||
/// FIX-6 Stage 2a: clasificador call→ChainAction del fee unificado (taxonomía
|
||||
/// OCAFB-2). Vive aquí porque el runtime es el único sitio donde `RuntimeCall`
|
||||
/// es concreto. Fallback conservador: `Write` (fee estándar — un wildcard
|
||||
/// jamás regala subsidio; mismo principio que la alerta TLB de Ergon).
|
||||
pub struct OriginCallClassifier;
|
||||
|
||||
impl origin_chain_fees::ClassifyChainAction<crate::RuntimeCall> for OriginCallClassifier {
|
||||
fn classify(call: &crate::RuntimeCall) -> origin_chain_fees::ChainAction {
|
||||
use origin_chain_fees::ChainAction;
|
||||
match call {
|
||||
crate::RuntimeCall::Balances(
|
||||
pallet_balances::Call::transfer_allow_death { .. }
|
||||
| pallet_balances::Call::transfer_keep_alive { .. }
|
||||
| pallet_balances::Call::transfer_all { .. },
|
||||
) => ChainAction::Transfer,
|
||||
crate::RuntimeCall::Revive(
|
||||
pallet_revive::Call::instantiate { .. }
|
||||
| pallet_revive::Call::instantiate_with_code { .. }
|
||||
// call_index 10: el deploy vía la capa EVM (Code::Upload). Sin
|
||||
// este arm caería en el fallback Write y evadiría el
|
||||
// entitlement-gate DeployContract (HIGH del audit Stage 2a).
|
||||
| pallet_revive::Call::eth_instantiate_with_code { .. },
|
||||
) => ChainAction::DeployContract,
|
||||
crate::RuntimeCall::Revive(
|
||||
pallet_revive::Call::call { .. }
|
||||
| pallet_revive::Call::eth_call { .. }
|
||||
// eth_transact: envoltorio EVM genérico (puede ser call/deploy/
|
||||
// transfer). Lo tratamos como ejecución de contrato — su
|
||||
// entitlement (Free=false) y subsidio coinciden con CallContract;
|
||||
// refinar cuando la capa EVM se cablee plenamente.
|
||||
| pallet_revive::Call::eth_transact { .. },
|
||||
) => ChainAction::CallContract,
|
||||
crate::RuntimeCall::Revive(pallet_revive::Call::upload_code { .. }) => {
|
||||
ChainAction::UploadContractCode
|
||||
}
|
||||
_ => ChainAction::Write,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
/// On-charge transaction canonical: usa `pallet_transaction_payment` default
|
||||
/// charge mechanism. Phase 2.4 lo refactor canonical para integrar
|
||||
/// `OriginFees` trait (CHN-8 multi-tier fee matrix).
|
||||
/// FIX-6 Stage 2a (cierra V2-CA3): `OriginFeeAdapter` reemplaza al
|
||||
/// `FungibleAdapter` default — la fórmula OCAFB-5 entra al fee-path VIVO:
|
||||
/// el firmante paga su fracción según (HardwareTier × ChainAction ×
|
||||
/// SubscriptionTier, tiers leídos ON-CHAIN de `TierProfiles`, jamás
|
||||
/// caller-supplied), y el subsidy pool `og/subsy` (alimentado por la
|
||||
/// emisión Index 13) cubre la fracción subsidiada — Inmutable #16 en cada
|
||||
/// transacción. `OU = ()` deliberado: burn matrix CHN-2 ratificada = gas
|
||||
/// fees 100% burn (el burn total = fee constitucional completo, deflación
|
||||
/// que contrapesa la emisión Stage 1).
|
||||
type OnChargeTransaction =
|
||||
pallet_transaction_payment::FungibleAdapter<crate::Balances, ()>;
|
||||
origin_chain_fees::OriginFeeAdapter<crate::Balances, (), OriginCallClassifier>;
|
||||
type OperationalFeeMultiplier = OperationalFeeMultiplier;
|
||||
type WeightToFee = frame_support::weights::IdentityFee<Balance>;
|
||||
type LengthToFee = frame_support::weights::IdentityFee<Balance>;
|
||||
@@ -368,6 +414,10 @@ impl origin_chain_provenance::Config for Runtime {
|
||||
|
||||
impl origin_chain_fees::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
/// FIX-6 Stage 2a: la MISMA constante que alimenta la emisión (Index 13)
|
||||
/// — pool-alimentación y pool-consumo apuntan a la misma cuenta og/subsy
|
||||
/// (jamás re-declarar el literal: un typo divergiría las cuentas).
|
||||
type SubsidyPalletId = EmissionSubsidyPalletId;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -45,6 +45,7 @@ use crate::{
|
||||
};
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use origin_chain_types::{HardwareTier, SubscriptionTier};
|
||||
use sp_genesis_builder::PresetId;
|
||||
|
||||
// ============================================================
|
||||
@@ -200,10 +201,14 @@ fn build_genesis(
|
||||
endowed_accounts: Vec<(ChainAccountId, Balance)>,
|
||||
validators: Vec<(ChainAccountId, AuraId, GrandpaId)>,
|
||||
sudo_key: ChainAccountId,
|
||||
// FIX-6 Stage 2a: perfiles de fee a sembrar en block 0. En dev/local las
|
||||
// cuentas de prueba arrancan en un tier que puede desplegar contratos (Free
|
||||
// está gated de DeployContract por Inmutable #37); en mainnet va vacío.
|
||||
initial_fee_profiles: Vec<(ChainAccountId, HardwareTier, SubscriptionTier)>,
|
||||
) -> RuntimeGenesisConfig {
|
||||
use crate::{
|
||||
AuraConfig, BalancesConfig, GrandpaConfig, ReviveConfig, SessionConfig, SudoConfig,
|
||||
SystemConfig, TransactionPaymentConfig,
|
||||
AuraConfig, BalancesConfig, FeesConfig, GrandpaConfig, ReviveConfig, SessionConfig,
|
||||
SudoConfig, SystemConfig, TransactionPaymentConfig,
|
||||
};
|
||||
RuntimeGenesisConfig {
|
||||
system: SystemConfig::default(),
|
||||
@@ -212,6 +217,10 @@ fn build_genesis(
|
||||
dev_accounts: None,
|
||||
},
|
||||
transaction_payment: TransactionPaymentConfig::default(),
|
||||
// FIX-6 Stage 2a: perfiles de tier sembrados (el fee-path vivo los lee).
|
||||
fees: FeesConfig {
|
||||
initial_profiles: initial_fee_profiles,
|
||||
},
|
||||
aura: AuraConfig {
|
||||
authorities: validators.iter().map(|(_, aura, _)| aura.clone()).collect(),
|
||||
},
|
||||
@@ -257,7 +266,11 @@ pub fn development_config_genesis() -> RuntimeGenesisConfig {
|
||||
build_genesis(
|
||||
vec![(alice_account.clone(), 100_000_000_u128 * Ogc::ONE)],
|
||||
vec![alice_authority],
|
||||
alice_account, // FIX-5: Alice = sudo key (Root) en dev
|
||||
alice_account.clone(), // FIX-5: Alice = sudo key (Root) en dev
|
||||
// FIX-6 Stage 2a: Alice arranca Ultra para poder desplegar contratos
|
||||
// out-of-the-box en --dev (los Slices 3.x de contract testing no se
|
||||
// bloquean por el entitlement-gate Free+DeployContract).
|
||||
vec![(alice_account, HardwareTier::Ultra, SubscriptionTier::Ultra)],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -279,13 +292,22 @@ pub fn local_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
build_genesis(
|
||||
vec![
|
||||
(alice.clone(), endowment),
|
||||
(bob, endowment),
|
||||
(charlie, endowment),
|
||||
(dave, endowment),
|
||||
(eve, endowment),
|
||||
(bob.clone(), endowment),
|
||||
(charlie.clone(), endowment),
|
||||
(dave.clone(), endowment),
|
||||
(eve.clone(), endowment),
|
||||
],
|
||||
vec![alice_authority, bob_authority],
|
||||
alice, // FIX-5: Alice = sudo key (Root) en local_testnet
|
||||
alice.clone(), // FIX-5: Alice = sudo key (Root) en local_testnet
|
||||
// FIX-6 Stage 2a: las 5 cuentas dev arrancan Ultra para testing
|
||||
// completo (contratos + compute) sin upgrade manual.
|
||||
vec![
|
||||
(alice, HardwareTier::Ultra, SubscriptionTier::Ultra),
|
||||
(bob, HardwareTier::Ultra, SubscriptionTier::Ultra),
|
||||
(charlie, HardwareTier::Ultra, SubscriptionTier::Ultra),
|
||||
(dave, HardwareTier::Ultra, SubscriptionTier::Ultra),
|
||||
(eve, HardwareTier::Ultra, SubscriptionTier::Ultra),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -335,6 +357,10 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig {
|
||||
// (multisig humano). PRE-LAUNCH Q1 2027: David ratifica la key real +
|
||||
// sunset a governance (pallet-collective/referenda).
|
||||
foundation_treasury,
|
||||
// FIX-6 Stage 2a: mainnet arranca SIN perfiles sembrados — todos en el
|
||||
// Default conservador (Desktop+Free) hasta que la attestation real
|
||||
// (Mathi PerceptionAdapter + DID binding) asigne tiers verificados.
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,10 @@ pub const IMPL_NAME: &str = "origin-chain-charon";
|
||||
|
||||
/// Canonical runtime version canonical. Bumped cada forkless upgrade via
|
||||
/// `set_code` extrinsic governance canonical.
|
||||
pub const RUNTIME_VERSION: u32 = 1;
|
||||
/// v2: FIX-6 Stage 2a — fee unificado (OriginFeeAdapter reemplaza al
|
||||
/// FungibleAdapter: cambia el COMPORTAMIENTO del cobro de fees, no el formato
|
||||
/// de extrinsic — TRANSACTION_VERSION intacto).
|
||||
pub const RUNTIME_VERSION: u32 = 2;
|
||||
|
||||
/// Canonical authoring version per Substrate semver canonical.
|
||||
pub const AUTHORING_VERSION: u32 = 1;
|
||||
@@ -794,8 +797,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn runtime_version_starts_at_one_canonical() {
|
||||
assert_eq!(RUNTIME_VERSION, 1);
|
||||
fn runtime_version_v2_fee_unificado() {
|
||||
// v2 = FIX-6 Stage 2a (fee unificado). El formato de extrinsic NO
|
||||
// cambió: solo el associated type OnChargeTransaction.
|
||||
assert_eq!(RUNTIME_VERSION, 2);
|
||||
assert_eq!(TRANSACTION_VERSION, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user