feat(economía): techo duro 1B + fees con dientes (David Level A 2026-07-03, RV 6→7)

Dos decisiones económicas ratificadas por David, verificadas en fuente:

1. TECHO DURO 1B (Opción A) — la emisión TRANSFIERE del pool Ecosystem finito
   (og/ecosy, 400M pre-asignado en génesis), JAMÁS mint_into. La aserción de
   compilación de 1.000M deja de ser promesa de génesis y se vuelve ley que la
   producción de bloques obedece: el total issuance nunca crece, el pool se
   agota en ~7.6 años (10 OGC/bloque), después economía circular pura. Cuando el
   pool no cubre el reward completo, libera el remanente y emite
   EcosystemPoolDepleted una vez. Evento renombrado BlockRewardMinted →
   BlockRewardReleased (honestidad: es redistribución, no acuñación).
   - origin-chain-emission: Currency.transfer (Expendable) desde ecosystem_account;
     +EcosystemPalletId config, +DepletionAnnounced storage one-shot, +2 tests
     (hard_cap_pool_se_agota_y_para, depletion_event_se_anuncia_una_sola_vez).
   - runtime: EmissionEcosystemPalletId (og/ecosy) wired; génesis asigna el 40%
     a la cuenta PalletId determinista (sin llave humana), no al placeholder ECO!.

2. FEES CON DIENTES — WeightToFee/LengthToFee de IdentityFee (polvo ~10^-10 OGC,
   #16 = adorno) a ConstantMultiplier calibrado: un transfer simple ≈ 1 MILLI_OGC
   (0.001 OGC). Consecuencias: el descuento Pi5 (0.1×) = ahorro real (0.0001 OGC);
   la quema del 100% de gas es deflación medible desde el testnet; el spam cuesta
   OGC real. Factor 7e6 (weight) + 1e11 (length). Test de regresión
   weight_to_fee_transfer_tiene_dientes_no_polvo blinda contra revertir a polvo.
   Calibrado a pesos placeholder — re-tunear con benchmarks reales (leftover FIX-2).

RUNTIME_VERSION 6→7. Workspace 275 tests verde, 0 fallos. TRANSACTION_VERSION intacto.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Fable 5 committed 2026-07-03 11:58:55 -05:00
1 parent 7b8863fd47
commit 1fdc47e859
4 files changed
+282 -65

No files matched your search

+41 -10
View File
@@ -167,6 +167,23 @@ parameter_types! {
/// benchmarking real (leftover FIX-2).
pub FeeMultiplierOne: pallet_transaction_payment::Multiplier =
pallet_transaction_payment::Multiplier::from_u32(1);
/// Factor WeightToFee (base units OGC por unidad de ref_time). Calibrado
/// (David 2026-07-03) para que OCAFB-5 tenga DIENTES: con IdentityFee el fee
/// base de un transfer era ~2×10^-10 OGC (polvo cósmico) y el #16 anti-
/// clasista era un adorno. Con este factor, un transfer simple (ExtrinsicBase
/// 108.157M + transfer_keep_alive ~39M ≈ 1.47×10^8 ref_time) cuesta:
/// 1.47×10^8 × 7×10^6 ≈ 1.03×10^15 base units ≈ **1 MILLI_OGC (0.001 OGC)**.
/// Consecuencias: el descuento Pi5 (0.1×) = ahorro real (0.0001 OGC); la
/// quema del 100% de gas es deflación medible desde el testnet; el spam
/// cuesta OGC real. Calibrado a pesos placeholder — re-tunear con benchmarks
/// reales (leftover FIX-2). `ConstantMultiplier` usa solo ref_time.
pub const WeightFeeFactor: Balance = 7_000_000;
/// Factor LengthToFee: 10^11 base units por byte ⇒ 1 KiB ≈ 0.0001 OGC. Da
/// costo real (anti-bloat) a extrinsics grandes (código de contrato, remarks)
/// sin dominar el fee de un tx normal (150 bytes ≈ 0.000015 OGC).
pub const LengthFeeFactor: Balance = 100_000_000_000;
}
/// FIX-6 Stage 2a: clasificador call→ChainAction del fee unificado (taxonomía
@@ -223,8 +240,11 @@ impl pallet_transaction_payment::Config for Runtime {
type OnChargeTransaction =
origin_chain_fees::OriginFeeAdapter<crate::Balances, (), OriginCallClassifier>;
type OperationalFeeMultiplier = OperationalFeeMultiplier;
type WeightToFee = frame_support::weights::IdentityFee<Balance>;
type LengthToFee = frame_support::weights::IdentityFee<Balance>;
/// Calibrado (David 2026-07-03): un transfer simple ≈ 1 MILLI_OGC. NUNCA
/// `IdentityFee` — con 18 decimales el fee base era polvo (~10^-10 OGC) y
/// anulaba el #16. Ver `WeightFeeFactor`.
type WeightToFee = frame_support::weights::ConstantMultiplier<Balance, WeightFeeFactor>;
type LengthToFee = frame_support::weights::ConstantMultiplier<Balance, LengthFeeFactor>;
/// NUNCA `()` aquí: colapsa `NextFeeMultiplier` a 0 (ver `FeeMultiplierOne`).
type FeeMultiplierUpdate = pallet_transaction_payment::ConstFeeMultiplier<FeeMultiplierOne>;
type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight<Runtime>;
@@ -481,22 +501,31 @@ impl origin_chain_governance::Config for Runtime {
// origin_chain_emission::Config — FIX-6 Stage 1 (audit V2-CA2)
// ============================================================
//
// En cada bloque mintea EmissionBlockReward y lo reparte: 70% al autor (el
// validador Aura), 20% al treasury (og/gvtry, la MISMA cuenta de governance),
// 10% al subsidy pool (og/subsy, proof-of-equity que financia el subsidio de
// hardware #16 — ortogonal a la suscripción, David 2026-06-10). Cierra "los
// validadores ganan 0 OGC". FindAuthor = el mapeo Aura→validador de session.
// En cada bloque LIBERA EmissionBlockReward del pool Ecosystem (og/ecosy, 400M)
// y lo reparte: 70% al autor (el validador Aura), 20% al treasury (og/gvtry, la
// MISMA cuenta de governance), 10% al subsidy pool (og/subsy, proof-of-equity
// que financia el subsidio de hardware #16 — ortogonal a la suscripción, David
// 2026-06-10). Cierra "los validadores ganan 0 OGC". FindAuthor = el mapeo
// Aura→validador de session.
//
// BlockReward 10 OGC × ~5.256M bloques/año ≈ 52.6M OGC/año ≈ 5.26% inflación
// sobre el genesis de 1B (materializa la emisión del 40% Ecosystem BM V2).
// TECHO DURO 1B (David 2026-07-03, Opción A): transfer del pool finito, NUNCA
// mint. BlockReward 10 OGC × ~5.256M bloques/año ≈ 52.6M OGC/año ⇒ el pool de
// 400M se agota en ~7.6 años; después, economía circular. El total issuance
// JAMÁS crece — la aserción de compilación de 1B se cumple a rajatabla.
// Stage 2 introduce pallet_staking NPoS sobre este substrate.
parameter_types! {
/// Recompensa por bloque: 10 OGC (~5% inflación anual).
/// Recompensa por bloque: 10 OGC. NO es inflación — es el ritmo de
/// LIBERACIÓN del pool Ecosystem finito (400M): 10 × ~5.256M bloques/año ≈
/// 52.6M/año ⇒ el pool se agota en ~7.6 años (techo duro 1B, David 2026-07-03).
pub const EmissionBlockReward: Balance = 10 * OGC;
/// 70% al autor (validador Aura).
pub const EmissionAuthorShareBps: u16 = 7_000;
/// 20% al treasury (el residual 10% va al subsidy pool).
pub const EmissionTreasuryShareBps: u16 = 2_000;
/// PalletId del pool Ecosystem (40% = 400M en génesis) — la FUENTE finita de
/// las recompensas. La emisión transfiere de aquí, jamás mintea (techo duro).
pub const EmissionEcosystemPalletId: frame_support::PalletId =
frame_support::PalletId(*b"og/ecosy");
/// PalletId del subsidy pool (proof-of-equity, #16 anti-clasista).
pub const EmissionSubsidyPalletId: frame_support::PalletId =
frame_support::PalletId(*b"og/subsy");
@@ -510,6 +539,8 @@ impl origin_chain_emission::Config for Runtime {
type BlockReward = EmissionBlockReward;
type AuthorShareBps = EmissionAuthorShareBps;
type TreasuryShareBps = EmissionTreasuryShareBps;
// La FUENTE finita: el pool Ecosystem (og/ecosy) = 400M pre-asignado en génesis.
type EcosystemPalletId = EmissionEcosystemPalletId;
// Reusa el MISMO vault de governance (og/gvtry) para el treasury.
type TreasuryPalletId = GovernanceTreasuryPalletId;
type SubsidyPalletId = EmissionSubsidyPalletId;
@@ -39,7 +39,7 @@
#![cfg(feature = "std")]
use crate::{
configs::{AuraId, GovernanceTreasuryPalletId, GrandpaId},
configs::{AuraId, EmissionEcosystemPalletId, GovernanceTreasuryPalletId, GrandpaId},
opaque::SessionKeys,
Balance, ChainAccountId, Ogc, RuntimeGenesisConfig,
};
@@ -360,7 +360,12 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig {
account_from_sr25519(bytes)
};
let ecosystem_pool = make_placeholder(b"ECO!"); // Ecosystem/Rewards Pool canonical
// El 40% Ecosystem (400M) se asigna a la cuenta PalletId `og/ecosy` — la
// MISMA de la que `pallet-origin-emission` LIBERA las recompensas de bloque
// (transfer, techo duro 1B; David 2026-07-03). No es un placeholder key-
// controlado: es la cuenta determinista del pool, sin llave humana.
let ecosystem_pool: ChainAccountId =
EmissionEcosystemPalletId::get().into_account_truncating();
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)
+34 -7
View File
@@ -243,7 +243,15 @@ pub const IMPL_NAME: &str = "origin-chain-charon";
/// `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;
/// v7: economía Level A (David 2026-07-03, 2 decisiones) — (a) **TECHO DURO 1B**:
/// la emisión TRANSFIERE del pool Ecosystem finito (og/ecosy, 400M) en vez de
/// `mint_into`; el total issuance jamás crece, el pool se agota en ~7.6 años,
/// después economía circular (evento `BlockRewardReleased` + `EcosystemPoolDepleted`
/// one-shot). (b) **fees con dientes**: `WeightToFee`/`LengthToFee` de `IdentityFee`
/// (polvo ~10^-10 OGC) a `ConstantMultiplier` calibrado — un transfer ≈ 1 MILLI_OGC,
/// el #16 Pi5 0.1× = ahorro real, quema deflacionaria medible. Comportamiento ⇒
/// spec bump; TRANSACTION_VERSION intacto.
pub const RUNTIME_VERSION: u32 = 7;
/// Canonical authoring version per Substrate semver canonical.
pub const AUTHORING_VERSION: u32 = 1;
@@ -835,12 +843,12 @@ mod tests {
use super::*;
#[test]
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);
fn runtime_version_v7_hard_cap_and_fee_teeth() {
// v7 = economía Level A (David 2026-07-03): techo duro 1B (emisión =
// transfer del pool Ecosystem, no mint) + fees con dientes (ConstantMultiplier
// calibrado, transfer ≈ 1 MILLI_OGC). Cambios de comportamiento, no de
// formato de extrinsic ⇒ TRANSACTION_VERSION intacto.
assert_eq!(RUNTIME_VERSION, 7);
assert_eq!(TRANSACTION_VERSION, 1);
}
@@ -952,6 +960,25 @@ mod tests {
assert!(!next.is_zero());
}
#[test]
fn weight_to_fee_transfer_tiene_dientes_no_polvo() {
// Calibración David 2026-07-03 (v7): un transfer simple debe costar ~1
// MILLI_OGC, NO el polvo ~10^-10 OGC de IdentityFee (que anulaba el #16).
// Guard de regresión: si alguien revierte a IdentityFee, el fee cae a
// ~1.5×10^8 base units (muy por debajo de MILLI_OGC/2) y esto rompe.
use frame_support::weights::{Weight, WeightToFee as _};
// transfer ≈ ExtrinsicBaseWeight (108.157M) + transfer_keep_alive (~39M).
let transfer_weight = Weight::from_parts(108_157_000 + 39_051_000, 0);
let fee = <<Runtime as pallet_transaction_payment::Config>::WeightToFee>::weight_to_fee(
&transfer_weight,
);
assert!(
fee >= MILLI_OGC / 2 && fee <= MILLI_OGC * 3,
"fee de transfer = {fee} base units; esperado ~1 MILLI_OGC ({MILLI_OGC}), \
ni polvo ni exorbitante"
);
}
#[test]
fn revive_deposit_per_byte_canonical_milli_ogc() {
// Asset Hub pattern adapted OGC tokenomics — 1 MILLI_OGC per byte canonical.