From 7b8863fd4737061573eda9fb003b4e8681678c2d Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Fri, 3 Jul 2026 10:22:05 -0500 Subject: [PATCH] =?UTF-8?q?fix(runtime+emission):=20audit=20ultra=202026-0?= =?UTF-8?q?7-02=20=E2=80=94=203=20fixes=20verificados=20en=20fuente=20(RV?= =?UTF-8?q?=205=E2=86=926)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit ultra-profundo (Fable 5, refute-by-default): 3 dimensiones completadas (economía, fee-path, attestation) → 23 hallazgos crudos → verificación manual en fuente de los HIGH. Los 3 inequívocos, corregidos: (a) FeeMultiplierUpdate = () COLAPSABA NextFeeMultiplier a 0 tras el bloque 1 [HIGH]: impl Convert for () devuelve Default (=0) y on_finalize hace *fm = convert(*fm) — el componente weight-fee desaparecía de TODA la cadena desde el bloque 2. Verificado en pallet-transaction- payment-43.0.0 src (líneas 158-171, 446-448). Fix: ConstFeeMultiplier<1> + test de regresión fee_multiplier_update_never_collapses_to_zero. (b) NativeToEthRatio = 1_000_000 con comentario "OGC 12 decimals" [HIGH]: OGC tiene 18 decimales (Ogc::DECIMALS=18) = precisión ETH ⇒ ratio 1:1. El 10^6 (herencia del template de 12 dec) distorsionaba TODO valor visto por la capa EVM ×10^6. Fix: ConstU32<1> + test derivado de la delta de decimales (guard de coherencia, no número mágico). (c) Emisión: let _ = mint_into ×3 descartaba resultados [MEDIUM] mientras TotalEmitted y el evento BlockRewardMinted registraban incondicionalmente ⇒ la contabilidad podía mentir si un mint fallaba (e.g. bajo ED). Fix: TotalEmitted + evento registran SOLO lo realmente acuñado. RUNTIME_VERSION 5→6 (cambios de comportamiento; TRANSACTION_VERSION intacto). Workspace verde: 272 checks, 0 fallos. Pendientes de decisión de David (Level A economía): emisión-mint vs Ecosystem 400M pool (doble fuente del mismo propósito) · IdentityFee dust con 18 decimales (calibración WeightToFee). Audit continúa post-reset (6 dimensiones + verify formal de los 23). Co-Authored-By: Claude Fable 5 --- crates/origin-chain-emission/src/lib.rs | 35 ++++++++++----- crates/origin-chain-runtime/src/configs.rs | 19 +++++++- crates/origin-chain-runtime/src/lib.rs | 51 +++++++++++++++++----- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/crates/origin-chain-emission/src/lib.rs b/crates/origin-chain-emission/src/lib.rs index 856335f..447158e 100644 --- a/crates/origin-chain-emission/src/lib.rs +++ b/crates/origin-chain-emission/src/lib.rs @@ -164,18 +164,31 @@ pub mod pallet { .saturating_sub(author_amount) .saturating_sub(treasury_amount); - // mint_into aumenta el total issuance (emisión real). - let _ = T::Currency::mint_into(&author, author_amount); - let _ = T::Currency::mint_into(&Self::treasury_account(), treasury_amount); - let _ = T::Currency::mint_into(&Self::subsidy_account(), subsidy_amount); + // mint_into aumenta el total issuance (emisión real). Puede + // fallar (e.g. destino por debajo del existential deposit): + // TotalEmitted y el evento SOLO registran lo que de verdad + // se acuñó, o la contabilidad miente (audit ultra 2026-07-02). + let author_minted = + T::Currency::mint_into(&author, author_amount).unwrap_or_default(); + let treasury_minted = + T::Currency::mint_into(&Self::treasury_account(), treasury_amount) + .unwrap_or_default(); + let subsidy_minted = + T::Currency::mint_into(&Self::subsidy_account(), subsidy_amount) + .unwrap_or_default(); - TotalEmitted::::mutate(|t| *t = t.saturating_add(reward)); - Self::deposit_event(Event::BlockRewardMinted { - author, - author_amount, - treasury_amount, - subsidy_amount, - }); + let total_minted = author_minted + .saturating_add(treasury_minted) + .saturating_add(subsidy_minted); + if total_minted > 0 { + TotalEmitted::::mutate(|t| *t = t.saturating_add(total_minted)); + Self::deposit_event(Event::BlockRewardMinted { + author, + author_amount: author_minted, + treasury_amount: treasury_minted, + subsidy_amount: subsidy_minted, + }); + } } } // Placeholder de minteo (pendiente benchmarking real, leftover FIX-2) + diff --git a/crates/origin-chain-runtime/src/configs.rs b/crates/origin-chain-runtime/src/configs.rs index d934f00..47df042 100644 --- a/crates/origin-chain-runtime/src/configs.rs +++ b/crates/origin-chain-runtime/src/configs.rs @@ -157,6 +157,16 @@ impl pallet_timestamp::Config for Runtime { parameter_types! { /// Operational fee multiplier canonical: 5x baseline canonical Substrate. pub const OperationalFeeMultiplier: u8 = 5; + /// Multiplier constante = 1 para `FeeMultiplierUpdate`. El unit type `()` + /// NO es "sin ajuste": `impl Convert for ()` devuelve + /// `Default` (= 0), y `on_finalize` hace `*fm = convert(*fm)` — con `()` + /// el `NextFeeMultiplier` colapsa a 0 tras el primer bloque y el componente + /// weight-fee desaparece de TODA la cadena (audit ultra 2026-07-02, HIGH). + /// `ConstFeeMultiplier` congela el multiplier en 1 (sin congestion + /// pricing dinámico todavía); `TargetedFeeAdjustment` llega con el + /// benchmarking real (leftover FIX-2). + pub FeeMultiplierOne: pallet_transaction_payment::Multiplier = + pallet_transaction_payment::Multiplier::from_u32(1); } /// FIX-6 Stage 2a: clasificador call→ChainAction del fee unificado (taxonomía @@ -215,7 +225,8 @@ impl pallet_transaction_payment::Config for Runtime { type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = frame_support::weights::IdentityFee; type LengthToFee = frame_support::weights::IdentityFee; - type FeeMultiplierUpdate = (); + /// NUNCA `()` aquí: colapsa `NextFeeMultiplier` a 0 (ver `FeeMultiplierOne`). + type FeeMultiplierUpdate = pallet_transaction_payment::ConstFeeMultiplier; type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; } @@ -577,7 +588,11 @@ impl pallet_revive::Config for Runtime { // ── EVM & Address Mapping ────────────────────────────────── type AddressMapper = pallet_revive::AccountId32Mapper; type ChainId = ConstU64<9_120_005>; // Origin Metaverse EIP-155 unique - type NativeToEthRatio = ConstU32<1_000_000>; // OGC 12 decimals → ETH 18 decimals + /// OGC tiene 18 decimales (`Ogc::DECIMALS = 18`, ogc.rs:29) = la misma + /// precisión que ETH → ratio 1:1. El valor anterior `1_000_000` venía del + /// template (nativo de 12 decimales) y distorsionaba TODO valor visto por + /// la capa EVM ×10^6 (audit ultra 2026-07-02, HIGH). + type NativeToEthRatio = ConstU32<1>; type AllowEVMBytecode = ConstBool; // Solidity contracts canonical /// EthGasEncoder canonical: gas ↔ balance conversion para EVM-style /// transactions. Empty tuple `()` usa default canonical pallet-revive diff --git a/crates/origin-chain-runtime/src/lib.rs b/crates/origin-chain-runtime/src/lib.rs index 209e262..7f4ba16 100644 --- a/crates/origin-chain-runtime/src/lib.rs +++ b/crates/origin-chain-runtime/src/lib.rs @@ -236,7 +236,14 @@ pub const IMPL_NAME: &str = "origin-chain-charon"; /// 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; +/// v6: audit ultra 2026-07-02 (3 fixes) — (a) `FeeMultiplierUpdate`: `()` colapsaba +/// `NextFeeMultiplier` a 0 tras el bloque 1 (weight-fee desaparecía de la cadena); +/// ahora `ConstFeeMultiplier<1>`. (b) `NativeToEthRatio` 1_000_000→1 (OGC = 18 +/// decimales, no 12; la capa EVM distorsionaba valores ×10^6). (c) emisión: +/// `TotalEmitted` + evento registran SOLO lo realmente acuñado (resultados de +/// `mint_into` ya no se descartan). Cambia comportamiento ⇒ spec bump; +/// TRANSACTION_VERSION intacto. +pub const RUNTIME_VERSION: u32 = 6; /// Canonical authoring version per Substrate semver canonical. pub const AUTHORING_VERSION: u32 = 1; @@ -828,11 +835,12 @@ mod tests { use super::*; #[test] - 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); + fn runtime_version_v6_ultra_audit_fixes() { + // v6 = 3 fixes del audit ultra 2026-07-02: ConstFeeMultiplier (el `()` + // colapsaba NextFeeMultiplier a 0), NativeToEthRatio=1 (OGC 18 dec), + // y emisión con contabilidad honesta de mint_into. Cambios de + // comportamiento, no de formato de extrinsic ⇒ TRANSACTION_VERSION intacto. + assert_eq!(RUNTIME_VERSION, 6); assert_eq!(TRANSACTION_VERSION, 1); } @@ -913,12 +921,35 @@ mod tests { } #[test] - fn revive_native_to_eth_ratio_one_million_canonical() { - // OGC 12 decimals → ETH 18 decimals = ratio 10^6 canonical. - // Mapping balance OGC↔ETH-equivalent units para EVM compat. + fn revive_native_to_eth_ratio_one_to_one() { + // OGC tiene 18 decimales (Ogc::DECIMALS) = la precisión de ETH ⇒ ratio + // 1:1. El 10^6 anterior era herencia del template (nativo 12 dec) y + // distorsionaba la capa EVM ×10^6 (audit ultra 2026-07-02, fix v6). + // Guard de coherencia: el ratio DEBE derivar de la delta de decimales. use frame_support::traits::Get; let ratio: u32 = <::NativeToEthRatio>::get(); - assert_eq!(ratio, 1_000_000_u32); + const ETH_DECIMALS: u32 = 18; + let expected = 10_u32.pow(ETH_DECIMALS - origin_chain_types::Ogc::DECIMALS); + assert_eq!(ratio, expected); + assert_eq!(ratio, 1_u32); + } + + #[test] + fn fee_multiplier_update_never_collapses_to_zero() { + // Regresión del audit ultra 2026-07-02: `FeeMultiplierUpdate = ()` + // devolvía Default (= 0) en cada on_finalize ⇒ NextFeeMultiplier + // colapsaba a 0 tras el bloque 1 y el weight-fee desaparecía de toda + // la cadena. El update JAMÁS puede mapear 1 → 0. + use pallet_transaction_payment::Multiplier; + use sp_runtime::traits::{Convert, Zero}; + let one = Multiplier::from_u32(1); + let next = + <::FeeMultiplierUpdate as Convert< + Multiplier, + Multiplier, + >>::convert(one); + assert_eq!(next, one, "el multiplier debe permanecer en 1, no colapsar"); + assert!(!next.is_zero()); } #[test]