fix(runtime+emission): audit ultra 2026-07-02 — 3 fixes verificados en fuente (RV 5→6)
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<A, B: Default> Convert<A,B> 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a0364db99
commit
7b8863fd47
3 files changed
+82
-23
No files matched your search
@@ -164,18 +164,31 @@ pub mod pallet {
|
|||||||
.saturating_sub(author_amount)
|
.saturating_sub(author_amount)
|
||||||
.saturating_sub(treasury_amount);
|
.saturating_sub(treasury_amount);
|
||||||
|
|
||||||
// mint_into aumenta el total issuance (emisión real).
|
// mint_into aumenta el total issuance (emisión real). Puede
|
||||||
let _ = T::Currency::mint_into(&author, author_amount);
|
// fallar (e.g. destino por debajo del existential deposit):
|
||||||
let _ = T::Currency::mint_into(&Self::treasury_account(), treasury_amount);
|
// TotalEmitted y el evento SOLO registran lo que de verdad
|
||||||
let _ = T::Currency::mint_into(&Self::subsidy_account(), subsidy_amount);
|
// 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::<T>::mutate(|t| *t = t.saturating_add(reward));
|
let total_minted = author_minted
|
||||||
Self::deposit_event(Event::BlockRewardMinted {
|
.saturating_add(treasury_minted)
|
||||||
author,
|
.saturating_add(subsidy_minted);
|
||||||
author_amount,
|
if total_minted > 0 {
|
||||||
treasury_amount,
|
TotalEmitted::<T>::mutate(|t| *t = t.saturating_add(total_minted));
|
||||||
subsidy_amount,
|
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) +
|
// Placeholder de minteo (pendiente benchmarking real, leftover FIX-2) +
|
||||||
|
|||||||
@@ -157,6 +157,16 @@ impl pallet_timestamp::Config for Runtime {
|
|||||||
parameter_types! {
|
parameter_types! {
|
||||||
/// Operational fee multiplier canonical: 5x baseline canonical Substrate.
|
/// Operational fee multiplier canonical: 5x baseline canonical Substrate.
|
||||||
pub const OperationalFeeMultiplier: u8 = 5;
|
pub const OperationalFeeMultiplier: u8 = 5;
|
||||||
|
/// Multiplier constante = 1 para `FeeMultiplierUpdate`. El unit type `()`
|
||||||
|
/// NO es "sin ajuste": `impl<A, B: Default> Convert<A, B> 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<One>` 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
|
/// 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 OperationalFeeMultiplier = OperationalFeeMultiplier;
|
||||||
type WeightToFee = frame_support::weights::IdentityFee<Balance>;
|
type WeightToFee = frame_support::weights::IdentityFee<Balance>;
|
||||||
type LengthToFee = frame_support::weights::IdentityFee<Balance>;
|
type LengthToFee = frame_support::weights::IdentityFee<Balance>;
|
||||||
type FeeMultiplierUpdate = ();
|
/// NUNCA `()` aquí: colapsa `NextFeeMultiplier` a 0 (ver `FeeMultiplierOne`).
|
||||||
|
type FeeMultiplierUpdate = pallet_transaction_payment::ConstFeeMultiplier<FeeMultiplierOne>;
|
||||||
type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight<Runtime>;
|
type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight<Runtime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,7 +588,11 @@ impl pallet_revive::Config for Runtime {
|
|||||||
// ── EVM & Address Mapping ──────────────────────────────────
|
// ── EVM & Address Mapping ──────────────────────────────────
|
||||||
type AddressMapper = pallet_revive::AccountId32Mapper<Self>;
|
type AddressMapper = pallet_revive::AccountId32Mapper<Self>;
|
||||||
type ChainId = ConstU64<9_120_005>; // Origin Metaverse EIP-155 unique
|
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<true>; // Solidity contracts canonical
|
type AllowEVMBytecode = ConstBool<true>; // Solidity contracts canonical
|
||||||
/// EthGasEncoder canonical: gas ↔ balance conversion para EVM-style
|
/// EthGasEncoder canonical: gas ↔ balance conversion para EVM-style
|
||||||
/// transactions. Empty tuple `()` usa default canonical pallet-revive
|
/// transactions. Empty tuple `()` usa default canonical pallet-revive
|
||||||
|
|||||||
@@ -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
|
/// 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
|
/// con el TierProfile REAL on-chain del account, no caller-supplied). Cambia la
|
||||||
/// metadata del Runtime API FeesApi; método aditivo ⇒ TRANSACTION_VERSION intacto.
|
/// 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.
|
/// Canonical authoring version per Substrate semver canonical.
|
||||||
pub const AUTHORING_VERSION: u32 = 1;
|
pub const AUTHORING_VERSION: u32 = 1;
|
||||||
@@ -828,11 +835,12 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_version_v5_fees_api_account_alignment() {
|
fn runtime_version_v6_ultra_audit_fixes() {
|
||||||
// v5 = FeesApi.query_fee_for_account (estima con el TierProfile real
|
// v6 = 3 fixes del audit ultra 2026-07-02: ConstFeeMultiplier (el `()`
|
||||||
// on-chain). Método aditivo al Runtime API ⇒ los calls/extrinsics
|
// colapsaba NextFeeMultiplier a 0), NativeToEthRatio=1 (OGC 18 dec),
|
||||||
// EXISTENTES no cambian su formato ⇒ TRANSACTION_VERSION intacto.
|
// y emisión con contabilidad honesta de mint_into. Cambios de
|
||||||
assert_eq!(RUNTIME_VERSION, 5);
|
// comportamiento, no de formato de extrinsic ⇒ TRANSACTION_VERSION intacto.
|
||||||
|
assert_eq!(RUNTIME_VERSION, 6);
|
||||||
assert_eq!(TRANSACTION_VERSION, 1);
|
assert_eq!(TRANSACTION_VERSION, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,12 +921,35 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn revive_native_to_eth_ratio_one_million_canonical() {
|
fn revive_native_to_eth_ratio_one_to_one() {
|
||||||
// OGC 12 decimals → ETH 18 decimals = ratio 10^6 canonical.
|
// OGC tiene 18 decimales (Ogc::DECIMALS) = la precisión de ETH ⇒ ratio
|
||||||
// Mapping balance OGC↔ETH-equivalent units para EVM compat.
|
// 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;
|
use frame_support::traits::Get;
|
||||||
let ratio: u32 = <<Runtime as pallet_revive::Config>::NativeToEthRatio>::get();
|
let ratio: u32 = <<Runtime as pallet_revive::Config>::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 =
|
||||||
|
<<Runtime as pallet_transaction_payment::Config>::FeeMultiplierUpdate as Convert<
|
||||||
|
Multiplier,
|
||||||
|
Multiplier,
|
||||||
|
>>::convert(one);
|
||||||
|
assert_eq!(next, one, "el multiplier debe permanecer en 1, no colapsar");
|
||||||
|
assert!(!next.is_zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user