fix(genesis): float operacional al sudo_key — cierra MEDIUM de la auditoría ultra

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 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.8 committed 2026-06-21 21:15:36 -05:00
1 parent 8e72496901
commit 1a0364db99
1 file changed
+64 -4
@@ -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");
}
}