From 1a0364db99681a215f229ce7c52119e34e9ce009 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Sun, 21 Jun 2026 21:15:36 -0500 Subject: [PATCH] =?UTF-8?q?fix(genesis):=20float=20operacional=20al=20sudo?= =?UTF-8?q?=5Fkey=20=E2=80=94=20cierra=20MEDIUM=20de=20la=20auditor=C3=ADa?= =?UTF-8?q?=20ultra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La auditoría ultra adversarial del commit 8e72496 cazó un MEDIUM real: al mover los 250M Foundation al vault og/gvtry, el sudo_key (FNDT) quedó con balance 0 — pero el OriginFeeAdapter cobra tx-fees al firmante, así que el sudo_key (que debe dispatchar los calls Root del sunset pre-mainnet: set de keys reales, seed del council) no podría pagar sus propios extrinsics (withdraw_fee falla upfront; Pays::No no rescata, es post-dispatch). Verificado en fuente por el refutador. Fix: el 25% Foundation se carva en vault (grueso, DAO-governed) + SUDO_OPERATIONAL_FLOAT (1M OGC al sudo_key FNDT para fees operacionales). Sub-split de FOUNDATION_ALLOCATION (NO una 6ª allocation) ⇒ la suma 1B se preserva (const assert + test). 3 tests nuevos: el sudo_key tiene float (no inoperante) + balances suman 1B + vault recibe el grueso. Monto del float ratificable por David pre-launch Q1 2027. 271 workspace tests verde, 0 regresión. Co-Authored-By: Claude Opus 4.8 --- .../src/genesis_config_presets.rs | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/crates/origin-chain-runtime/src/genesis_config_presets.rs b/crates/origin-chain-runtime/src/genesis_config_presets.rs index 97ef63d..deea688 100644 --- a/crates/origin-chain-runtime/src/genesis_config_presets.rs +++ b/crates/origin-chain-runtime/src/genesis_config_presets.rs @@ -71,6 +71,18 @@ pub const FOUNDING_TEAM_ALLOCATION: Balance = 150_000_000_u128 * Ogc::ONE; /// **5% Exchange Liquidity canonical** = 50M OGC. pub const EXCHANGE_LIQUIDITY_ALLOCATION: Balance = 50_000_000_u128 * Ogc::ONE; +/// Float operacional del sudo_key (Foundation multisig interim) = 1M OGC. +/// +/// **NO es una 6ª allocation** — es un SUB-SPLIT del 25% Foundation +/// ([`FOUNDATION_ALLOCATION`]): el grueso va al vault DAO-governed (og/gvtry) y +/// este float al sudo_key (FNDT) para que pueda pagar los tx-fees de sus propios +/// extrinsics Root durante el interim pre-sunset (set de keys reales, seed del +/// council, transición a governance) — el `OriginFeeAdapter` cobra al firmante, +/// así que un sudo_key con balance 0 quedaría inoperante desde el bloque 0. +/// `vault + float == FOUNDATION_ALLOCATION` ⇒ la suma 1B no cambia. Monto exacto +/// ratificado por David pre-launch Q1 2027. +pub const SUDO_OPERATIONAL_FLOAT: Balance = 1_000_000_u128 * Ogc::ONE; + /// Compile-time assertion canonical: las allocations canonical suman exactamente /// el genesis supply canonical. Si alguna constante cambia, el code no compila /// hasta que las allocations vuelvan a sumar 1B OGC canonical. @@ -84,6 +96,11 @@ const _: () = { total == GENESIS_SUPPLY, "BM V2 allocations canonical must sum a 1B OGC canonical" ); + // El float operacional es un sub-split del 25% Foundation, no lo excede. + assert!( + SUDO_OPERATIONAL_FLOAT < FOUNDATION_ALLOCATION, + "el float operacional del sudo_key debe ser un sub-split del 25% Foundation" + ); }; // ============================================================ @@ -356,6 +373,11 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig { // SOLO como sudo_key interim (Root) hasta el sunset a governance pre-mainnet. let governance_vault: ChainAccountId = GovernanceTreasuryPalletId::get().into_account_truncating(); + // El 25% Foundation se carva: el grueso al vault DAO-governed, un float + // operacional al sudo_key (FNDT) para que pueda pagar los fees de sus propios + // extrinsics Root pre-sunset (el OriginFeeAdapter cobra al firmante; un sudo_key + // con balance 0 quedaría inoperante). `vault + float == FOUNDATION_ALLOCATION`. + let vault_allocation: Balance = FOUNDATION_ALLOCATION - SUDO_OPERATIONAL_FLOAT; // 5 initial validators canonical Pi 5 placeholders (Dream House Studios + universities). let validators = vec![ @@ -370,16 +392,18 @@ pub fn mainnet_genesis_config_genesis() -> RuntimeGenesisConfig { // BM V2 allocation canonical exact: 40/25/15/15/5 → 1B OGC total vec![ (ecosystem_pool, ECOSYSTEM_ALLOCATION), - (governance_vault, FOUNDATION_ALLOCATION), // 250M → vault og/gvtry (DAO-governed) + (governance_vault, vault_allocation), // grueso del 25% → vault og/gvtry (DAO-governed) + (foundation_treasury.clone(), SUDO_OPERATIONAL_FLOAT), // float fees del sudo_key (FNDT) (public_sale, PUBLIC_SALE_ALLOCATION), (founding_team, FOUNDING_TEAM_ALLOCATION), (exchange_liquidity, EXCHANGE_LIQUIDITY_ALLOCATION), ], validators, // FIX-5: el placeholder FNDT (Foundation multisig humano) = sudo key interim - // (Root) en mainnet. PRE-LAUNCH Q1 2027: David ratifica la key real + sunset - // a governance (pallet-collective/referenda). El treasury (250M) ya NO vive - // aquí — está en el vault og/gvtry, gastable solo vía governance. + // (Root) en mainnet, con un float operacional para fees. PRE-LAUNCH Q1 2027: + // David ratifica la key real + sunset a governance (DC GOVERNANCE_SUNSET_V1). + // El grueso del treasury (250M − float) vive en el vault og/gvtry, gastable + // solo vía governance. foundation_treasury, // FIX-6 Stage 2a: mainnet arranca SIN perfiles sembrados — todos en el // Default conservador (Desktop+Free) hasta que la attestation real @@ -481,4 +505,40 @@ mod tests { // sp_keyring::Sr25519Keyring::Alice.public().0). assert_eq!(ALICE_SR25519[0..4], [0xd4, 0x35, 0x93, 0xc7]); } + + #[test] + fn mainnet_sudo_key_tiene_float_operacional_para_fees() { + // Regresión cerrada (auditoría ultra MEDIUM): al mover los 250M al vault, + // el sudo_key (FNDT) NO debe quedar con balance 0 — el OriginFeeAdapter + // cobra fees al firmante, y el sudo_key debe poder pagar sus extrinsics + // Root pre-sunset. Tiene el float operacional. + let cfg = mainnet_genesis_config_genesis(); + let sudo_key = cfg.sudo.key.expect("mainnet tiene sudo_key"); + let sudo_balance = cfg + .balances + .balances + .iter() + .find(|(acc, _)| *acc == sudo_key) + .map(|(_, bal)| *bal) + .expect("el sudo_key debe estar endowed (si no, queda inoperante)"); + assert_eq!(sudo_balance, SUDO_OPERATIONAL_FLOAT); + assert!(sudo_balance > 0, "sudo_key con balance 0 = no paga fees = inoperante"); + } + + #[test] + fn mainnet_balances_suman_genesis_supply_1b() { + // El split Foundation (vault + float) no altera la suma total: 1B OGC. + let cfg = mainnet_genesis_config_genesis(); + let total: Balance = cfg.balances.balances.iter().map(|(_, b)| *b).sum(); + assert_eq!(total, GENESIS_SUPPLY, "el split vault/float preserva la suma 1B"); + } + + #[test] + fn mainnet_vault_recibe_el_grueso_del_25pct_foundation() { + // El grueso del 25% Foundation (250M − float) va al vault og/gvtry, y + // vault + float == FOUNDATION_ALLOCATION (cero pérdida del 25%). + let vault_allocation = FOUNDATION_ALLOCATION - SUDO_OPERATIONAL_FLOAT; + assert_eq!(vault_allocation + SUDO_OPERATIONAL_FLOAT, FOUNDATION_ALLOCATION); + assert!(vault_allocation > SUDO_OPERATIONAL_FLOAT, "el grueso va al vault"); + } }