From d6dd08da66abf0bbc247279a39a673b346b5bb00 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Fri, 3 Jul 2026 12:04:03 -0500 Subject: [PATCH] =?UTF-8?q?fix(emission):=20integrity=5Ftest=20guard=20?= =?UTF-8?q?=E2=80=94=20shares=20autor+treasury=20=E2=89=A4=20100%=20(audit?= =?UTF-8?q?=20ultra)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hallazgo MEDIUM del audit ultra: si un cambio de config hiciera AuthorShareBps + TreasuryShareBps > 10_000 bps, el `subsidy = reward.saturating_sub(author). saturating_sub(treasury)` daría subsidy = 0 SILENCIOSAMENTE (el pool #16 se quedaría sin fondos sin ruido). El hook `integrity_test()` lo convierte en un fallo ruidoso del integrity-check del runtime. +test. Co-Authored-By: Claude Fable 5 --- crates/origin-chain-emission/src/lib.rs | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/origin-chain-emission/src/lib.rs b/crates/origin-chain-emission/src/lib.rs index 2064b65..0ebc2bd 100644 --- a/crates/origin-chain-emission/src/lib.rs +++ b/crates/origin-chain-emission/src/lib.rs @@ -180,9 +180,23 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + /// Guard de integridad (audit ultra 2026-07-02): las shares autor + + /// treasury JAMÁS pueden exceder 100% — el residual es el subsidy pool + /// (`subsidy = reward − author − treasury`). Si un futuro cambio de + /// config las hiciera sumar > 10_000 bps, el `saturating_sub` daría + /// subsidy = 0 SILENCIOSAMENTE. Esto lo convierte en un fallo ruidoso + /// en el build/integrity-check del runtime. + fn integrity_test() { + assert!( + T::AuthorShareBps::get().saturating_add(T::TreasuryShareBps::get()) <= 10_000, + "emisión: AuthorShareBps + TreasuryShareBps no pueden exceder 100% \ + (el residual es el subsidy pool #16)" + ); + } + /// Cada bloque: identifica al autor Aura (desde los pre-runtime digests), - /// mintea el BlockReward y lo reparte. Si no hay autor (e.g. bloque de - /// inherentes sin pre-digest), no mintea. + /// libera el BlockReward del pool Ecosystem y lo reparte. Si no hay autor + /// (e.g. bloque de inherentes sin pre-digest), no libera. fn on_initialize(_n: BlockNumberFor) -> Weight { // Kill-switch (governance): si la emisión está pausada, no libera nada. // Lee 1 storage value; default `false` ⇒ activa (cero regresión). @@ -496,6 +510,14 @@ mod tests { }); } + #[test] + fn integrity_test_shares_no_exceden_100pct() { + new_test_ext().execute_with(|| { + // Mock: 7000 + 2000 = 9000 ≤ 10000 ⇒ pasa (el residual 1000 = subsidy). + >::integrity_test(); + }); + } + #[test] fn depletion_event_se_anuncia_una_sola_vez() { new_test_ext_with_pool(500).execute_with(|| {