feat(governance): RV16 — el override del veto se decide al cerrar la ventana, con 4/5 de quien participa

Cierra L-11 (override real del veto). David aprobó el 4/5 el 2026-10-01: «exigir una mayoría
calificada de 4/5 en el consejo para anular un veto de emergencia es el cerrojo adecuado antes
de retirar la llave Sudo».

Qué cambia
- La ventana de override tiene dos lados: revertir (override_council_veto) y sostener
  (uphold_council_veto, nuevo). Quien votó Nay puede sostener el veto; antes no tenía voz.
- Nada se decide al cruzar un umbral. finalize_override (nuevo) decide UNA vez, al cerrar la
  ventana: se revierte si override >= ceil(4/5 * max(quórum, participación)).
- El quórum se fija al vetar: max(Aye + Nay del voto original, MinOverrideQuorum). Abstain no
  cuenta. Así el bond que el veto devuelve no basta para revertirlo y un upgrade no lo mueve.
- MinOverrideQuorum en el runtime = 100M OGC (el 10% de PILAR_4 §6.6); integrity_test exige
  que supere MIN_PROPOSAL_STAKE.
- El periodo de voto ordinario dura al menos 14 días (MIN = DEFAULT) y el veto solo cabe dentro
  de él. Un voto de peso 0 en la ventana se rechaza.
- spec_version 16. Pesos de uphold_council_veto y finalize_override medidos (frame-omni-bencher
  0.15.0, 50 steps x 20 repeats); los demás re-medidos en el mismo banco.

La revisión adversarial (nivel 1 de la directiva) tumbó la primera versión
- Bloqueante: la regla de quórum no era monótona y votar «sostener» podía REVERTIR el veto.
  Ahora sostener nunca ayuda a revertir (test con la frontera exacta 2250/2251).
- El mínimo de 48 h regalaba la vía de emergencia a cualquiera; ahora 14 días.
- Las abstenciones inflaban el quórum; el quórum podía cambiar con un upgrade; la spec no subía.
- 18 contrafactuales (mutaciones V1..V18), todos en rojo sin su pieza.

Divergencia que queda abierta (D1 del roadmap V2): PILAR_4 §6.6 ratificó «override por 2/3» y
el código implementa 4/5, que es lo que David confirmó. La constitución del DAO lo fija.

Tests: gobernanza 78 (94 con runtime-benchmarks), runtime 44.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CharonandClaude Opus 5.5 committed 2026-10-07 10:30:18 -05:00
1 parent a694cbd9f0
commit 3771a15165
8 files changed
+1081 -173

No files matched your search

@@ -94,7 +94,13 @@ mod benchmarks {
fund::<T>(&caller);
#[extrinsic_call]
submit_proposal(RawOrigin::Signed(caller), [1u8; 32], [2u8; 32], 1_000u64, MIN_PROPOSAL_STAKE);
submit_proposal(
RawOrigin::Signed(caller),
[1u8; 32],
[2u8; 32],
MIN_VOTING_PERIOD_BLOCKS,
MIN_PROPOSAL_STAKE,
);
assert_eq!(Proposals::<T>::iter().count(), 1);
}
@@ -230,7 +236,7 @@ mod benchmarks {
seed_proposal::<T>(id, ProposalStatus::Rejected, 0u64);
VetoRejected::<T>::insert(id, ());
// RV15: ventana abierta, peso respaldado por saldo y la lista de respaldo casi
// llena (peor caso). Y rama cara: el override ALCANZA el umbral y aprueba.
// llena (peor caso). RV16: el voto ya no decide nada; eso es finalize_override.
OverrideDeadline::<T>::insert(id, 1_000_000u64);
fund::<T>(&caller);
seed_backing::<T>(
@@ -243,10 +249,6 @@ mod benchmarks {
override_council_veto(RawOrigin::Signed(caller.clone()), id, MIN_PROPOSAL_STAKE);
assert!(OverrideVotes::<T>::contains_key(id, &caller));
assert!(matches!(
Proposals::<T>::get(id).unwrap().status,
ProposalStatus::Approved
));
}
#[benchmark]
@@ -332,5 +334,48 @@ mod benchmarks {
assert_eq!(VoteLocks::<T>::get(&votante).len() as u32, max - vencidos);
}
#[benchmark]
fn uphold_council_veto() {
// Mismo peor caso que override_council_veto, por el otro lado.
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Rejected, 0u64);
VetoRejected::<T>::insert(id, ());
OverrideDeadline::<T>::insert(id, 1_000_000u64);
fund::<T>(&caller);
seed_backing::<T>(
&caller,
T::MaxActiveVotes::get().saturating_sub(1),
1_000_000u64,
);
#[extrinsic_call]
uphold_council_veto(RawOrigin::Signed(caller.clone()), id, MIN_PROPOSAL_STAKE);
assert!(UpholdVotes::<T>::contains_key(id, &caller));
}
#[benchmark]
fn finalize_override() {
// Rama cara: el override REVIERTE el veto (escribe la propuesta y el evento).
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Rejected, 0u64);
VetoRejected::<T>::insert(id, ());
OverrideDeadline::<T>::insert(id, 1u64);
OverrideQuorum::<T>::insert(id, T::MinOverrideQuorum::get());
OverrideStakeTotal::<T>::insert(id, T::MinOverrideQuorum::get().max(1));
frame_system::Pallet::<T>::set_block_number(2u32.into());
#[extrinsic_call]
finalize_override(RawOrigin::Signed(caller), id);
assert!(matches!(
Proposals::<T>::get(id).unwrap().status,
ProposalStatus::Approved
));
assert!(!OverrideDeadline::<T>::contains_key(id));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+286 -87
View File
@@ -23,7 +23,10 @@
//! - 5.2: `pallet_collective` integration (Technical Council)
//! - 5.3: OIP stake minimum 1,000 OGC canonical
//! - 5.4: Stake-weighted voting (1 OGC = 1 vote, 14-day period)
//! - 5.5: Technical Council veto + override (2/3 voters)
//! - 5.5: Technical Council veto (2/3 del council) + override comunitario. PILAR_4 §6.6
//! ratificó «overridable por 2/3 voters»; el código implementa 4/5 de la participación
//! en la ventana, con quórum (RV16), y David confirmó el 4/5 el 2026-10-01. Lo que
//! el código hace y lo que dice la doctrina difieren hasta que la constitución lo fije.
//! - 5.6: AI governance agent interfaces (5 agents advise-only)
//! - 5.7: Emergency referendum (48h, 5/7 Technical Council)
//! - 5.8: Treasury disbursement extrinsics
@@ -208,6 +211,17 @@ pub mod pallet {
/// de quien votara en ella (demostrado por la revisión adversaria de RV15).
pub const MAX_VOTING_PERIOD_BLOCKS: u64 = 2 * DEFAULT_VOTING_PERIOD_BLOCKS;
/// RV16: periodo de voto mínimo de una propuesta ORDINARIA: 14 días, el que
/// ratifica PILAR_4 §6.6 («14-day period»).
///
/// Antes podía ser 0: el proponente enviaba, votaba en el mismo bloque y
/// finalizaba en el siguiente, aprobando con su único voto. La primera versión de
/// RV16 lo subió a 48 h y la revisión adversaria mostró por qué no basta: era la
/// duración de la vía de emergencia sin sus 5/7 endosos del council, y como el
/// veto solo cabe dentro del periodo, el proponente elegía cuánto tiempo tenía el
/// council para reaccionar. Las 48 h quedan solo para `submit_emergency_proposal`.
pub const MIN_VOTING_PERIOD_BLOCKS: u64 = DEFAULT_VOTING_PERIOD_BLOCKS;
/// Emergency voting period canonical canonical 48 hours per CHN-4 5.7.
/// 48 hours × 600 blocks/hour (@ 6s slot Aura) = 28,800 blocks.
pub const EMERGENCY_VOTING_PERIOD_BLOCKS: u64 = 48 * 600;
@@ -309,6 +323,14 @@ pub mod pallet {
#[pallet::constant]
type MaxActiveVotes: Get<u32>;
/// RV16: participación mínima (en OGC) para que un override pueda revertir
/// un veto. El quórum efectivo, fijado en el veto, es el MAYOR entre esto y la
/// participación del voto original (Aye + Nay): revertir al council exige al
/// menos tanta comunidad como la que votó. Es un parámetro de la constitución,
/// y `integrity_test` exige que supere el bond de una propuesta.
#[pallet::constant]
type MinOverrideQuorum: Get<u128>;
/// FIX-6: PalletId del que se deriva la cuenta-tesorería (vault). Cuenta
/// determinística sin private key — nadie la controla por wallet, solo la
/// governance vía treasury_disbursement (Root). Honor Inmutable #16: la
@@ -330,6 +352,19 @@ pub mod pallet {
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
/// RV16: el quórum de override tiene que superar el bond de una propuesta. Si
/// no, el proponente revertiría solo el veto con el bond que el propio veto le
/// devuelve (hallazgo C3), y con 0 bastaría una unidad base.
fn integrity_test() {
assert!(
T::MinOverrideQuorum::get() > MIN_PROPOSAL_STAKE,
"MinOverrideQuorum debe superar MIN_PROPOSAL_STAKE (hallazgo C3)"
);
}
}
/// RV15: por qué este pallet congela OGC.
#[pallet::composite_enum]
pub enum FreezeReason {
@@ -339,6 +374,25 @@ pub mod pallet {
GovernanceVote,
}
/// RV16: votos que SOSTIENEN el veto del council durante su ventana de
/// override (la otra mitad del recuento; antes no existía y quien votó Nay no
/// podía defender el veto).
#[pallet::storage]
pub type UpholdVotes<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
ProposalId,
Blake2_128Concat,
T::AccountId,
u128,
OptionQuery,
>;
/// RV16: OGC total que sostiene el veto, por propuesta.
#[pallet::storage]
pub type UpholdStakeTotal<T: Config> =
StorageMap<_, Blake2_128Concat, ProposalId, u128, ValueQuery>;
/// RV15: los votos vivos que respalda cada cuenta (ver [`VoteBacking`]). El
/// freeze `GovernanceVote` de la cuenta es el máximo de sus `amount`.
#[pallet::storage]
@@ -357,6 +411,16 @@ pub mod pallet {
pub type OverrideDeadline<T: Config> =
StorageMap<_, Blake2_128Concat, ProposalId, u64, OptionQuery>;
/// RV16: quórum del override de cada propuesta vetada, FIJADO en el veto:
/// max(Aye + Nay del voto original, `MinOverrideQuorum`). Se guarda y no se
/// recalcula al decidir: un runtime upgrade que cambie el parámetro no mueve la
/// meta de una ventana ya abierta (hallazgo de la revisión de RV16). La
/// abstención no cuenta: si contara, el mismo OGC abstenido en muchas propuestas
/// a la vez inflaría el quórum de todas sin aparecer en ninguna ventana.
#[pallet::storage]
pub type OverrideQuorum<T: Config> =
StorageMap<_, Blake2_128Concat, ProposalId, u128, OptionQuery>;
/// CHN-4 canonical: `Proposals<StorageMap<ProposalId, Proposal>>` canonical.
#[pallet::storage]
#[pallet::getter(fn proposal_of)]
@@ -595,15 +659,16 @@ pub mod pallet {
},
/// Council veto canonical overridden por community supermajority (Phase 5.5 v0.0.35).
///
/// Emitted canonical cuando override votes reach 4/5 total stake canonical canonical.
/// Proposal status canonical flipped from Rejected back to Voting OR Approved
/// (depending canonical en tally pre-veto).
/// RV16: lo emite `finalize_override` cuando, cerrada la ventana, revertir
/// reunió 4/5 del mayor entre el quórum y la participación. La propuesta pasa
/// a `Approved`. (Antes se emitía al cruzar el umbral en mitad de la ventana.)
CouncilVetoOverridden {
/// ProposalId canonical overridden.
/// Propuesta cuyo veto se revierte.
id: ProposalId,
/// Total override stake canonical at moment of override.
/// OGC que votó revertir.
override_stake: u128,
/// Total community stake canonical baseline (proposal.stake at submit).
/// RV16: participación total en la ventana (revertir + sostener). Antes era
/// la base del umbral viejo (stake + yes + nay + abstain de la propuesta).
total_community_stake: u128,
},
/// Emergency proposal submitted canonical (Phase 5.7 v0.0.35).
@@ -654,6 +719,30 @@ pub mod pallet {
/// OGC que sigue congelado por votos todavía vivos.
still_frozen: u128,
},
/// RV16: un voto en la ventana de override de un veto (para el indexer del
/// portal: antes un voto de override no emitía nada hasta el desenlace).
OverrideVoteCast {
/// Propuesta vetada.
id: ProposalId,
/// Quien vota.
voter: T::AccountId,
/// `true` = sostiene el veto; `false` = vota revertirlo.
uphold: bool,
/// OGC que respalda el voto (congelado hasta el cierre de la ventana).
weight: u128,
},
/// RV16: se cerró la ventana y el veto se SOSTIENE (el override no alcanzó
/// 4/5 de la participación o no hubo quórum).
CouncilVetoUpheld {
/// Propuesta vetada.
id: ProposalId,
/// OGC que votó revertir el veto.
override_stake: u128,
/// OGC que votó sostenerlo.
uphold_stake: u128,
/// Quórum que se exigía.
quorum: u128,
},
}
/// Pallet errors canonical.
@@ -729,6 +818,15 @@ pub mod pallet {
VotingPeriodTooLong,
/// RV15: la cuenta ya respalda `MaxActiveVotes` votos vivos.
TooManyActiveVotes,
/// RV16: el periodo de voto pedido es menor que `MIN_VOTING_PERIOD_BLOCKS`.
VotingPeriodTooShort,
/// RV16: la ventana de override sigue abierta; el resultado se decide al
/// cerrarse, no al cruzar un umbral a mitad de camino.
OverrideWindowOpen,
/// RV16: esta propuesta no tiene un override pendiente de decidir.
OverrideNotPending,
/// RV16: un voto de la ventana de override tiene que pesar más de 0.
ZeroWeightWindowVote,
}
/// Minimum stake canonical proposal submission per CHN-4 5.3.
@@ -763,6 +861,10 @@ pub mod pallet {
voting_period_blocks <= MAX_VOTING_PERIOD_BLOCKS,
Error::<T>::VotingPeriodTooLong
);
ensure!(
voting_period_blocks >= MIN_VOTING_PERIOD_BLOCKS,
Error::<T>::VotingPeriodTooShort
);
let proposer_acct = AccountId::new(account_bytes::<T>(&who));
let id = crate::derive_proposal_id(&proposer_acct, &title_hash, &body_hash);
@@ -1044,6 +1146,14 @@ pub mod pallet {
Error::<T>::ProposalAlreadyFinalized
);
// RV16: el veto solo cabe DURANTE el periodo de voto. Antes se podía vetar
// entre end_block y el finalize, y el resultado dependía de quién llamara
// primero (carrera entre el council y cualquiera que finalizara).
ensure!(
current_block_u64::<T>() <= proposal.end_block,
Error::<T>::VotingPeriodClosed
);
// Step 4: council size canonical > 0
let council_size = CouncilSize::<T>::get();
ensure!(council_size > 0, Error::<T>::InsufficientCouncilSize);
@@ -1070,6 +1180,10 @@ pub mod pallet {
let required = council_veto_threshold(council_size);
if live_count >= required {
// RV16: el quórum del override se fija AQUÍ, con la participación del
// voto original (Aye + Nay; la abstención no cuenta).
let turnout = proposal.yes_weight.saturating_add(proposal.nay_weight);
OverrideQuorum::<T>::insert(proposal_id, turnout.max(T::MinOverrideQuorum::get()));
// Veto threshold reached canonical → flip status canonical
proposal.status = ProposalStatus::Rejected;
Proposals::<T>::insert(proposal_id, proposal);
@@ -1142,21 +1256,21 @@ pub mod pallet {
Ok(())
}
/// CHN-4 5.5 v0.0.35 canonical: `override_council_veto` Phase 5.5 dispatchable.
/// CHN-4 5.5 + RV16: votar REVERTIR un veto del council durante su ventana.
///
/// Community supermajority canonical override de council veto. 4/5 total stake
/// canonical required canonical per CHN-4 5.5. Proposal status canonical flipped
/// desde Rejected canonical back to Approved canonical (community canonical >
/// council canonical veto on supermajority).
/// Doctrina: el council veta con 2/3 y la comunidad revierte con 4/5
/// (`docs/ORIGIN_DAO_DONDE_VIVE_V1.md`). RV16 hace real ese 4/5 (hallazgo C3 de
/// SUPER_AUDIT_V1, demostrado otra vez por la revisión de RV15):
/// - el voto NO voltea nada al cruzar un umbral; el resultado se decide al
/// cerrarse la ventana, en [`Pallet::finalize_override`];
/// - el 4/5 es de la PARTICIPACIÓN en la ventana (revertir + sostener), no de
/// los recuentos de la propia propuesta: antes contaba el bond del proponente,
/// que el veto le devuelve y que luego podía votar;
/// - quien quiere el veto puede defenderlo con [`Pallet::uphold_council_veto`];
/// - hace falta quórum.
///
/// 7-step canonical flow:
/// 1. ensure_signed canonical
/// 2. Get proposal (must canonical be Rejected previamente vía veto)
/// 3. Validate proposal.stake > 0 (baseline canonical)
/// 4. !AdvisorFlags canonical (humans-only canonical canonical)
/// 5. !OverrideVotes contains canonical (double-vote prevention)
/// 6. Insert vote + increment total stake
/// 7. Check threshold canonical 4/5: if >= → flip to Approved + emit CouncilVetoOverridden
/// El peso sale del saldo y queda congelado hasta el cierre de la ventana (RV15).
/// Una cuenta vota una vez en la ventana, en un solo lado.
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::override_council_veto())]
pub fn override_council_veto(
@@ -1165,73 +1279,7 @@ pub mod pallet {
stake_weight: u128,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut proposal = Proposals::<T>::get(proposal_id)
.ok_or(Error::<T>::ProposalNotFound)?;
ensure!(
matches!(proposal.status, ProposalStatus::Rejected),
Error::<T>::ProposalNotRejected
);
// Audit 2026-07-04: el override SOLO revierte un VETO del council, no
// un rechazo por voto popular. Sin esta marca, una propuesta que la
// comunidad votó EN CONTRA (Rejected vía finalize_proposal) podía
// resucitarse a Approved, invirtiendo el voto. Solo las vetadas aplican.
ensure!(
VetoRejected::<T>::contains_key(proposal_id),
Error::<T>::ProposalNotVetoRejected
);
ensure!(
!AdvisorFlags::<T>::contains_key(&who),
Error::<T>::AdvisorMayNotVote
);
ensure!(
!OverrideVotes::<T>::contains_key(proposal_id, &who),
Error::<T>::AlreadyOverrideVoted
);
// RV15: el override tiene ventana, y su peso sale del saldo y queda
// congelado hasta que la ventana cierre.
let deadline =
OverrideDeadline::<T>::get(proposal_id).ok_or(Error::<T>::OverrideWindowClosed)?;
ensure!(
current_block_u64::<T>() <= deadline,
Error::<T>::OverrideWindowClosed
);
Self::back_vote_with_funds(
&who,
proposal_id,
BackingKind::Override,
stake_weight,
deadline,
)?;
OverrideVotes::<T>::insert(proposal_id, &who, stake_weight);
let new_total = OverrideStakeTotal::<T>::get(proposal_id)
.saturating_add(stake_weight);
OverrideStakeTotal::<T>::insert(proposal_id, new_total);
let baseline = proposal.stake.saturating_add(proposal.yes_weight)
.saturating_add(proposal.nay_weight)
.saturating_add(proposal.abstain_weight);
let required = override_supermajority_threshold(baseline);
if new_total >= required {
proposal.status = ProposalStatus::Approved;
Proposals::<T>::insert(proposal_id, proposal);
Self::deposit_event(Event::CouncilVetoOverridden {
id: proposal_id,
override_stake: new_total,
total_community_stake: baseline,
});
}
Ok(())
Self::cast_override_window_vote(who, proposal_id, stake_weight, false)
}
/// CHN-4 5.7 v0.0.35 canonical: `submit_emergency_proposal` Phase 5.7 dispatchable.
@@ -1473,6 +1521,89 @@ pub mod pallet {
});
Ok(())
}
/// RV16: votar SOSTENER un veto del council durante su ventana de override.
/// Es la otra mitad del recuento: sin ella, quien votó Nay no podía defender
/// el veto y cualquier minoría con OGC suficiente lo revertía sola.
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::uphold_council_veto())]
pub fn uphold_council_veto(
origin: OriginFor<T>,
proposal_id: ProposalId,
stake_weight: u128,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::cast_override_window_vote(who, proposal_id, stake_weight, true)
}
/// RV16: decide un override cuando su ventana ya cerró. Lo puede llamar
/// cualquiera.
///
/// El veto se revierte (la propuesta pasa a `Approved`) solo si:
/// 1. la participación (revertir + sostener) alcanza el quórum, que es el
/// MAYOR entre `MinOverrideQuorum` y la participación del voto original
/// (Aye + Nay + Abstain); y
/// 2. revertir reúne al menos 4/5 de esa participación.
///
/// En cualquier otro caso el veto se sostiene. Se decide una sola vez.
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::finalize_override())]
pub fn finalize_override(origin: OriginFor<T>, proposal_id: ProposalId) -> DispatchResult {
ensure_signed(origin)?;
let mut proposal =
Proposals::<T>::get(proposal_id).ok_or(Error::<T>::ProposalNotFound)?;
ensure!(
matches!(proposal.status, ProposalStatus::Rejected),
Error::<T>::ProposalNotRejected
);
ensure!(
VetoRejected::<T>::contains_key(proposal_id),
Error::<T>::ProposalNotVetoRejected
);
let deadline =
OverrideDeadline::<T>::get(proposal_id).ok_or(Error::<T>::OverrideNotPending)?;
ensure!(
current_block_u64::<T>() > deadline,
Error::<T>::OverrideWindowOpen
);
let override_stake = OverrideStakeTotal::<T>::get(proposal_id);
let uphold_stake = UpholdStakeTotal::<T>::get(proposal_id);
let participation = override_stake.saturating_add(uphold_stake);
let quorum =
OverrideQuorum::<T>::get(proposal_id).ok_or(Error::<T>::OverrideNotPending)?;
// Se decide una sola vez: sin ventana, no hay override pendiente.
OverrideDeadline::<T>::remove(proposal_id);
OverrideQuorum::<T>::remove(proposal_id);
// Regla MONÓTONA: revertir necesita 4/5 del MAYOR entre el quórum y la
// participación. La primera versión exigía `participation >= quorum` y
// aparte el 4/5 de la participación, y la revisión adversaria demostró que
// así SOSTENER podía revertir el veto: un voto de sostener completaba el
// quórum que el lado revertir no alcanzaba solo, y no votar era la manera
// de defender el veto. Con max(quorum, participación), cada OGC que sostiene
// solo puede subir el listón de revertir, nunca bajarlo.
let overridden = override_stake > 0
&& override_stake >= override_supermajority_threshold(quorum.max(participation));
if overridden {
proposal.status = ProposalStatus::Approved;
Proposals::<T>::insert(proposal_id, proposal);
Self::deposit_event(Event::CouncilVetoOverridden {
id: proposal_id,
override_stake,
total_community_stake: participation,
});
} else {
Self::deposit_event(Event::CouncilVetoUpheld {
id: proposal_id,
override_stake,
uphold_stake,
quorum,
});
}
Ok(())
}
}
/// El mayor OGC respaldado por una lista de votos (0 si está vacía).
@@ -1485,6 +1616,74 @@ pub mod pallet {
<frame_system::Pallet<T>>::block_number().saturated_into::<u64>()
}
/// RV16 — un voto en la ventana de override de un veto, de cualquiera de los dos
/// lados. Comparte TODAS las comprobaciones: que la propuesta esté vetada (no
/// rechazada por voto popular), que la ventana esté abierta, que la cuenta no sea
/// advisor, que no haya votado ya en la ventana (en ningún lado), y que el peso
/// salga de su saldo y quede congelado hasta el cierre.
impl<T: Config> Pallet<T> {
fn cast_override_window_vote(
who: T::AccountId,
proposal_id: ProposalId,
stake_weight: u128,
uphold: bool,
) -> DispatchResult {
// Un voto de peso 0 en la ventana no decide nada y dejaría una entrada
// permanente (y a la cuenta sin poder votar después en esa ventana).
ensure!(stake_weight > 0, Error::<T>::ZeroWeightWindowVote);
let proposal = Proposals::<T>::get(proposal_id).ok_or(Error::<T>::ProposalNotFound)?;
ensure!(
matches!(proposal.status, ProposalStatus::Rejected),
Error::<T>::ProposalNotRejected
);
// Audit 2026-07-04: el override SOLO revierte un VETO del council, no
// un rechazo por voto popular.
ensure!(
VetoRejected::<T>::contains_key(proposal_id),
Error::<T>::ProposalNotVetoRejected
);
ensure!(
!AdvisorFlags::<T>::contains_key(&who),
Error::<T>::AdvisorMayNotVote
);
ensure!(
!OverrideVotes::<T>::contains_key(proposal_id, &who)
&& !UpholdVotes::<T>::contains_key(proposal_id, &who),
Error::<T>::AlreadyOverrideVoted
);
let deadline =
OverrideDeadline::<T>::get(proposal_id).ok_or(Error::<T>::OverrideWindowClosed)?;
ensure!(
current_block_u64::<T>() <= deadline,
Error::<T>::OverrideWindowClosed
);
Self::back_vote_with_funds(
&who,
proposal_id,
BackingKind::Override,
stake_weight,
deadline,
)?;
if uphold {
UpholdVotes::<T>::insert(proposal_id, &who, stake_weight);
UpholdStakeTotal::<T>::mutate(proposal_id, |t| *t = t.saturating_add(stake_weight));
} else {
OverrideVotes::<T>::insert(proposal_id, &who, stake_weight);
OverrideStakeTotal::<T>::mutate(proposal_id, |t| {
*t = t.saturating_add(stake_weight)
});
}
Self::deposit_event(Event::OverrideVoteCast {
id: proposal_id,
voter: who,
uphold,
weight: stake_weight,
});
Ok(())
}
}
/// RV15 — el OGC que respalda un voto.
impl<T: Config> Pallet<T> {
/// Exige que `weight` no supere el OGC LIBRE de `who` y lo deja congelado
@@ -48,6 +48,11 @@ impl origin_chain_governance::Config for Test {
type RuntimeFreezeReason = RuntimeFreezeReason;
// Bajo a propósito: el límite de votos vivos tiene que poder probarse.
type MaxActiveVotes = frame_support::traits::ConstU32<4>;
// RV16: quórum mínimo de override del mock = DOS bonds (2.000 OGC). Por encima del
// bond a propósito, como en el runtime (100M OGC frente a 1.000): con el quórum
// igual al bond, el proponente revertía solo con el bond devuelto (revisión de RV16).
type MinOverrideQuorum =
frame_support::traits::ConstU128<{ 2 * crate::pallet::MIN_PROPOSAL_STAKE }>;
type TreasuryPalletId = TestTreasuryPalletId;
type WeightInfo = ();
}
@@ -27,7 +27,13 @@ use origin_chain_types::{AccountId, VoteStance};
const TITLE_HASH: [u8; 32] = [10u8; 32];
const BODY_HASH: [u8; 32] = [20u8; 32];
const VOTING_PERIOD: u64 = 100;
/// RV16: el periodo de los tests es el mínimo legal (48 h). Antes era 100 bloques,
/// que hoy rechaza `submit_proposal`.
const VOTING_PERIOD: u64 = crate::pallet::MIN_VOTING_PERIOD_BLOCKS;
/// Un bloque claramente posterior al fin del periodo de `submit_default` (inicio
/// en el bloque 1). Sustituye a los `run_to_block(200)` que suponían un periodo
/// de 100 bloques.
const TRAS_EL_PERIODO: u64 = 1 + VOTING_PERIOD + 100;
fn submit_default(origin: frame_system::pallet_prelude::OriginFor<Test>) -> ProposalId {
let alice_bytes = alice_account_bytes();
@@ -187,7 +193,7 @@ fn scenario_7_finalize_approved_yes_majority_canonical() {
assert_ok!(Governance::cast_vote(charlie_origin(), id, VoteStance::Nay, 500));
// Advance past voting period
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
@@ -214,7 +220,7 @@ fn scenario_8_finalize_rejected_nay_majority_canonical() {
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 300));
assert_ok!(Governance::cast_vote(charlie_origin(), id, VoteStance::Nay, 700));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
@@ -239,7 +245,7 @@ fn scenario_8b_finalize_tie_rejected_canonical() {
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 500));
assert_ok!(Governance::cast_vote(charlie_origin(), id, VoteStance::Nay, 500));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
@@ -262,7 +268,7 @@ fn scenario_9_vote_closed_period_rejected_canonical() {
let id = submit_default(alice_origin());
// Advance past voting period
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
// Vote after end_block → VotingPeriodClosed
assert_noop!(
@@ -650,21 +656,29 @@ fn override_scenario_2_successful_override_canonical() {
let proposal = Proposals::<Test>::get(id).unwrap();
assert!(matches!(proposal.status, ProposalStatus::Rejected));
// Community supermajority override canonical
// baseline = stake + yes + nay + abstain = MIN_PROPOSAL_STAKE (no votes cast)
// required = ceil(baseline × 4 / 5)
let baseline = MIN_PROPOSAL_STAKE;
let required = (baseline * 4 + 4) / 5;
// Bob casts override con stake = required (enough)
// RV16: Bob vota revertir con el quórum mínimo (sin votos previos, el quórum
// es MinOverrideQuorum = 2 × MIN_PROPOSAL_STAKE en el mock) y nadie sostiene.
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
required,
2 * MIN_PROPOSAL_STAKE,
));
// El voto no voltea nada: el resultado se decide al cerrar la ventana.
assert!(matches!(
Proposals::<Test>::get(id).unwrap().status,
ProposalStatus::Rejected
));
let cierre = crate::OverrideDeadline::<Test>::get(id).expect("ventana");
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(charlie_origin(), id));
let proposal_after = Proposals::<Test>::get(id).unwrap();
assert!(matches!(proposal_after.status, ProposalStatus::Approved));
System::assert_last_event(RuntimeEvent::Governance(Event::CouncilVetoOverridden {
id,
override_stake: 2 * MIN_PROPOSAL_STAKE,
total_community_stake: 2 * MIN_PROPOSAL_STAKE,
}));
});
}
@@ -881,7 +895,7 @@ fn treasury_scenario_3_insufficient_balance_rejected_canonical() {
// Setup approved proposal
let id = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 1000));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
// Try disburse 200 (exceeds 100 balance)
@@ -908,7 +922,7 @@ fn treasury_scenario_4_successful_disbursement_canonical() {
// Setup approved proposal
let id = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 1000));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
// Disburse 300_000 canonical
@@ -951,7 +965,7 @@ fn override_no_puede_revertir_un_rechazo_por_voto_popular() {
// La comunidad la rechaza en las urnas (Nay > Aye).
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 100));
assert_ok!(Governance::cast_vote(charlie_origin(), id, VoteStance::Nay, 900));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
let p = Proposals::<Test>::get(id).unwrap();
assert!(matches!(p.status, ProposalStatus::Rejected), "rechazada por voto");
@@ -977,7 +991,7 @@ fn treasury_disbursement_es_single_use_anti_replay() {
assert_ok!(Governance::treasury_deposit(alice_origin(), 1_000_000));
let id = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 1000));
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
// Primer desembolso: OK.
@@ -1054,7 +1068,7 @@ fn bond_se_retiene_al_someter_y_se_devuelve_al_finalizar() {
assert_eq!(bal(&alice_account()), before - MIN_PROPOSAL_STAKE, "bond retenido");
assert_eq!(bal(&bond_acct), MIN_PROPOSAL_STAKE, "og/props tiene el bond");
// Finaliza (sin votos → Rejected) → bond devuelto íntegro.
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
assert_eq!(bal(&alice_account()), before, "bond devuelto al finalizar");
assert_eq!(bal(&bond_acct), 0, "cuenta-bond vacía");
@@ -1090,7 +1104,13 @@ fn submit_rechazado_si_no_alcanza_para_el_bond() {
)
.into();
assert_noop!(
Governance::submit_proposal(pobre, [7u8; 32], [8u8; 32], 100, MIN_PROPOSAL_STAKE),
Governance::submit_proposal(
pobre,
[7u8; 32],
[8u8; 32],
VOTING_PERIOD,
MIN_PROPOSAL_STAKE
),
Error::<Test>::StakeInsufficientBalance
);
});
@@ -1133,7 +1153,7 @@ fn scenario_10_lifecycle_walkthrough_canonical() {
assert_eq!(proposal.nay_weight, 1000);
// Step 3: advance + finalize canonical
run_to_block(200);
run_to_block(TRAS_EL_PERIODO);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
// Verify final status canonical
@@ -1252,7 +1272,7 @@ fn el_ogc_que_respalda_un_voto_no_sale_de_la_cuenta_mientras_el_voto_vive() {
fn el_freeze_real_es_el_mayor_de_los_votos_vivos_no_el_ultimo() {
new_test_ext().execute_with(|| {
let corta = submit_default(alice_origin());
let larga = propuesta_con_periodo(11, 3 * VOTING_PERIOD);
let larga = propuesta_con_periodo(11, 2 * VOTING_PERIOD);
let saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::cast_vote(
bob_origin(),
@@ -1292,7 +1312,7 @@ fn el_freeze_real_es_el_mayor_de_los_votos_vivos_no_el_ultimo() {
fn al_vencer_el_voto_grande_solo_queda_congelado_el_pequeno_que_sigue_vivo() {
new_test_ext().execute_with(|| {
let corta = submit_default(alice_origin());
let larga = propuesta_con_periodo(11, 3 * VOTING_PERIOD);
let larga = propuesta_con_periodo(11, 2 * VOTING_PERIOD);
assert_ok!(Governance::cast_vote(
bob_origin(),
larga,
@@ -1462,7 +1482,7 @@ fn un_voto_vencido_no_revive_con_el_siguiente() {
fn el_mismo_ogc_entero_vota_en_dos_propuestas() {
new_test_ext().execute_with(|| {
let p1 = submit_default(alice_origin());
let p2 = propuesta_con_periodo(11, 3 * VOTING_PERIOD);
let p2 = propuesta_con_periodo(11, 2 * VOTING_PERIOD);
let saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::cast_vote(
bob_origin(),
@@ -1615,7 +1635,7 @@ fn el_limite_es_de_votos_vivos_y_los_vencidos_no_ocupan_sitio() {
let corta = submit_default(alice_origin());
let mut largas = Vec::new();
for semilla in 11..15u8 {
largas.push(propuesta_con_periodo(semilla, 3 * VOTING_PERIOD));
largas.push(propuesta_con_periodo(semilla, 2 * VOTING_PERIOD));
}
assert_ok!(Governance::cast_vote(
bob_origin(),
@@ -1646,3 +1666,529 @@ fn el_limite_es_de_votos_vivos_y_los_vencidos_no_ocupan_sitio() {
assert_eq!(crate::VoteLocks::<Test>::get(bob_account()).len(), 4);
});
}
// ═══════════════════════════════════════════════════════════════
// RV16 — el override de un veto del council es un 4/5 REAL de la comunidad.
// Doctrina: council veta con 2/3, la comunidad revierte con 4/5. Hallazgo C3
// de SUPER_AUDIT_V1, demostrado otra vez por la revisión adversaria de RV15.
// ═══════════════════════════════════════════════════════════════
/// 1 OGC en unidades base (MIN_PROPOSAL_STAKE son 1.000 OGC). Los importes de los
/// tests de RV16 van en OGC reales: el quórum del mock es 1.000 OGC, y con unidades
/// base sueltas ningún override lo alcanzaría (me pasó al escribirlos).
const OGC: u128 = MIN_PROPOSAL_STAKE / 1_000;
/// Council de 1 (Alice) y propuesta de Alice vetada en el acto (bloque 1).
fn propuesta_vetada() -> (ProposalId, u64) {
use crate::pallet::CouncilRole;
use frame_support::dispatch::RawOrigin;
assert_ok!(Governance::add_council_member(
RawOrigin::Root.into(),
alice_account(),
CouncilRole::Chair,
));
let id = submit_default(alice_origin());
assert_ok!(Governance::veto_proposal(alice_origin(), id));
let cierre = crate::OverrideDeadline::<Test>::get(id).expect("el veto abre la ventana");
(id, cierre)
}
fn estado(id: ProposalId) -> ProposalStatus {
Proposals::<Test>::get(id).expect("propuesta").status
}
/// C3: el bond que el veto devuelve no basta para revertirlo. El quórum (aquí 2.000
/// OGC; 100M en el runtime) está por encima del bond (1.000) a propósito, y lo exige
/// `integrity_test`. La primera versión de este test usaba 800 OGC y pasaba porque
/// 800 no llegaba a un quórum IGUAL al bond: con el bond entero, revertía (revisión
/// de RV16). Ahora el bond entero no basta, y el doble sí, si nadie sostiene.
#[test]
fn el_bond_devuelto_no_basta_para_revertir_el_veto() {
for (revertir, revierte) in [(MIN_PROPOSAL_STAKE, false), (2 * MIN_PROPOSAL_STAKE, true)] {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
assert_ok!(Governance::override_council_veto(
alice_origin(),
id,
revertir
));
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(bob_origin(), id));
let aprobada = matches!(estado(id), ProposalStatus::Approved);
assert_eq!(aprobada, revierte, "el proponente revierte con {revertir}");
});
}
}
/// La regla es MONÓTONA: sostener el veto nunca ayuda a revertirlo. La primera
/// versión de RV16 contaba sostener para el quórum y la revisión demostró que, con
/// Aye 9.000 / Nay 1.000, Charlie revertía el veto votando SOSTENER con 1.000, y lo
/// defendía no votando. Ahora: revertir 9.000 contra quórum 10.000 pasa sin nadie
/// enfrente, y cada OGC que sostiene solo sube el listón (2.250 aún no, 2.251 sí).
#[test]
fn sostener_el_veto_nunca_ayuda_a_revertirlo() {
let casos = [
(0u128, true),
(1_000, true),
(2_000, true),
(2_250, true),
(2_251, false),
];
let mut anterior = true;
for (sostener, esperado) in casos {
let aprobada = new_test_ext().execute_with(|| {
use crate::pallet::CouncilRole;
use frame_support::dispatch::RawOrigin;
assert_ok!(Governance::add_council_member(
RawOrigin::Root.into(),
alice_account(),
CouncilRole::Chair,
));
let id = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Aye,
9_000 * OGC
));
assert_ok!(Governance::cast_vote(
charlie_origin(),
id,
VoteStance::Nay,
1_000 * OGC
));
assert_ok!(Governance::veto_proposal(alice_origin(), id));
let cierre = crate::OverrideDeadline::<Test>::get(id).expect("ventana");
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
9_000 * OGC
));
if sostener > 0 {
assert_ok!(Governance::uphold_council_veto(
charlie_origin(),
id,
sostener * OGC
));
}
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
matches!(estado(id), ProposalStatus::Approved)
});
assert_eq!(aprobada, esperado, "sostener {sostener} OGC");
assert!(
anterior || !aprobada,
"sostener {sostener} OGC ayudó a revertir"
);
anterior = aprobada;
}
}
/// Quien quiere el veto puede DEFENDERLO, y el 4/5 se mide sobre la participación:
/// 4/5 exacto revierte; un poco menos lo sostiene.
#[test]
fn quien_voto_nay_puede_sostener_el_veto_y_el_umbral_es_4_de_5_exacto() {
for (sostener, revierte) in [(2_000 * OGC, true), (2_000 * OGC + 1, false)] {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
// 8.000 OGC revertir (Bob) contra `sostener` (Charlie): 8.000/10.000 = 4/5 exacto.
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
8_000 * OGC
));
assert_ok!(Governance::uphold_council_veto(
charlie_origin(),
id,
sostener
));
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
let aprobada = matches!(estado(id), ProposalStatus::Approved);
assert_eq!(
aprobada, revierte,
"revertir 8.000 contra sostener {sostener}"
);
});
}
}
/// Un voto no voltea nada a mitad de ventana: el resultado se decide al cerrarla.
/// Antes, cruzar el umbral aprobaba en el acto, antes de que nadie pudiera sostener.
#[test]
fn el_override_se_decide_al_cerrar_la_ventana_no_al_cruzar_un_umbral() {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
let saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::override_council_veto(bob_origin(), id, saldo));
assert!(
matches!(estado(id), ProposalStatus::Rejected),
"100% y sigue vetada"
);
run_to_block(cierre);
assert_noop!(
Governance::finalize_override(alice_origin(), id),
Error::<Test>::OverrideWindowOpen
);
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
assert!(matches!(estado(id), ProposalStatus::Approved));
});
}
/// El quórum, fijado en el veto, es el MAYOR entre MinOverrideQuorum y la
/// participación del voto original contando Aye + Nay (la abstención NO cuenta: el
/// mismo OGC abstenido en muchas propuestas inflaría el quórum de todas sin aparecer
/// en ninguna ventana). Con Aye 1.000 + Nay 2.000 + Abstain 2.000, el quórum es
/// 3.000, y revertir solo, sin nadie que sostenga, necesita 4/5 de él: 2.400.
#[test]
fn el_quorum_es_aye_mas_nay_del_voto_original_y_se_fija_en_el_veto() {
for (revertir, revierte) in [(2_400 * OGC - 1, false), (2_400 * OGC, true)] {
new_test_ext().execute_with(|| {
use crate::pallet::CouncilRole;
use frame_support::dispatch::RawOrigin;
assert_ok!(Governance::add_council_member(
RawOrigin::Root.into(),
alice_account(),
CouncilRole::Chair,
));
let id = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(
alice_origin(),
id,
VoteStance::Aye,
1_000 * OGC
));
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Nay,
2_000 * OGC
));
assert_ok!(Governance::cast_vote(
charlie_origin(),
id,
VoteStance::Abstain,
2_000 * OGC
));
assert_ok!(Governance::veto_proposal(alice_origin(), id));
assert_eq!(crate::OverrideQuorum::<Test>::get(id), Some(3_000 * OGC));
let cierre = crate::OverrideDeadline::<Test>::get(id).expect("ventana");
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
revertir
));
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
let aprobada = matches!(estado(id), ProposalStatus::Approved);
assert_eq!(
aprobada, revierte,
"revertir {revertir} con quórum 3.000 OGC"
);
if !revierte {
System::assert_last_event(RuntimeEvent::Governance(Event::CouncilVetoUpheld {
id,
override_stake: revertir,
uphold_stake: 0,
quorum: 3_000 * OGC,
}));
}
});
}
}
/// El quórum que decide es el GUARDADO en el veto, no uno recalculado al final: un
/// upgrade que cambie el parámetro no mueve la meta de una ventana abierta.
#[test]
fn finalize_override_usa_el_quorum_fijado_en_el_veto() {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
assert_eq!(
crate::OverrideQuorum::<Test>::get(id),
Some(2 * MIN_PROPOSAL_STAKE)
);
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
MIN_PROPOSAL_STAKE
));
// Simula un quórum guardado más bajo: si finalize recalculara, no lo vería.
crate::OverrideQuorum::<Test>::insert(id, MIN_PROPOSAL_STAKE);
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
assert!(matches!(estado(id), ProposalStatus::Approved));
assert!(crate::OverrideQuorum::<Test>::get(id).is_none());
});
}
/// Los votos del mismo lado se SUMAN, en los dos lados.
#[test]
fn los_votos_del_mismo_lado_se_suman() {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
4_000 * OGC
));
assert_ok!(Governance::override_council_veto(
alice_origin(),
id,
4_000 * OGC
));
assert_ok!(Governance::uphold_council_veto(
charlie_origin(),
id,
2_000 * OGC
));
assert_eq!(crate::OverrideStakeTotal::<Test>::get(id), 8_000 * OGC);
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
assert!(
matches!(estado(id), ProposalStatus::Approved),
"8.000 de 10.000"
);
});
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
assert_ok!(Governance::override_council_veto(
bob_origin(),
id,
8_000 * OGC
));
assert_ok!(Governance::uphold_council_veto(
charlie_origin(),
id,
1_000 * OGC
));
assert_ok!(Governance::uphold_council_veto(
alice_origin(),
id,
1_000 * OGC + 1
));
assert_eq!(crate::UpholdStakeTotal::<Test>::get(id), 2_000 * OGC + 1);
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(alice_origin(), id));
assert!(
matches!(estado(id), ProposalStatus::Rejected),
"8.000 de 10.000+1"
);
});
}
/// Quien vota revertir no vuelve a votar en la ventana, ni en el mismo lado ni en el
/// otro: sin esto, el mismo OGC revertiría hasta MaxActiveVotes veces.
#[test]
fn quien_revierte_no_vuelve_a_votar_en_la_ventana() {
new_test_ext().execute_with(|| {
let (id, _) = propuesta_vetada();
let saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::override_council_veto(bob_origin(), id, saldo));
System::assert_last_event(RuntimeEvent::Governance(Event::OverrideVoteCast {
id,
voter: bob_account(),
uphold: false,
weight: saldo,
}));
assert_noop!(
Governance::override_council_veto(bob_origin(), id, saldo),
Error::<Test>::AlreadyOverrideVoted
);
assert_noop!(
Governance::uphold_council_veto(bob_origin(), id, 1),
Error::<Test>::AlreadyOverrideVoted
);
assert_eq!(crate::OverrideStakeTotal::<Test>::get(id), saldo);
assert_eq!(crate::VoteLocks::<Test>::get(bob_account()).len(), 1);
});
}
/// SOSTENER pasa por las mismas puertas que revertir: advisor, voto popular,
/// propuesta aún en votación, sin ventana, ventana cerrada y peso 0.
#[test]
fn sostener_pasa_por_las_mismas_puertas_que_revertir() {
new_test_ext().execute_with(|| {
use frame_support::dispatch::RawOrigin;
let (id, cierre) = propuesta_vetada();
assert_noop!(
Governance::uphold_council_veto(charlie_origin(), id, 0),
Error::<Test>::ZeroWeightWindowVote
);
assert_noop!(
Governance::override_council_veto(charlie_origin(), id, 0),
Error::<Test>::ZeroWeightWindowVote
);
assert_ok!(Governance::mark_as_advisor(
RawOrigin::Root.into(),
charlie_account()
));
assert_noop!(
Governance::uphold_council_veto(charlie_origin(), id, 1),
Error::<Test>::AdvisorMayNotVote
);
// Propuesta todavía en votación.
let viva = propuesta_con_periodo(12, VOTING_PERIOD);
assert_noop!(
Governance::uphold_council_veto(bob_origin(), viva, 1),
Error::<Test>::ProposalNotRejected
);
// Rechazada por voto popular: no es un veto.
run_to_block(fin_de(viva) + 1);
assert_ok!(Governance::finalize_proposal(bob_origin(), viva));
assert_noop!(
Governance::uphold_council_veto(bob_origin(), viva, 1),
Error::<Test>::ProposalNotVetoRejected
);
// Ventana cerrada.
run_to_block(cierre + 1);
assert_noop!(
Governance::uphold_council_veto(bob_origin(), id, 1),
Error::<Test>::OverrideWindowClosed
);
});
new_test_ext().execute_with(|| {
// Veto sin ventana registrada: falla cerrado también al sostener.
let id = submit_default(alice_origin());
let mut p = Proposals::<Test>::get(id).expect("propuesta");
p.status = ProposalStatus::Rejected;
Proposals::<Test>::insert(id, p);
crate::VetoRejected::<Test>::insert(id, ());
assert_noop!(
Governance::uphold_council_veto(bob_origin(), id, 1),
Error::<Test>::OverrideWindowClosed
);
});
}
/// Una cuenta vota una vez en la ventana, en un solo lado; el peso de sostener
/// también sale del saldo y queda congelado hasta el cierre.
#[test]
fn una_cuenta_vota_una_vez_en_la_ventana_y_sostener_tambien_congela() {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
let saldo = Balances::free_balance(charlie_account());
assert_noop!(
Governance::uphold_council_veto(charlie_origin(), id, saldo + 1),
Error::<Test>::VoteWeightExceedsBalance
);
assert_ok!(Governance::uphold_council_veto(charlie_origin(), id, 500));
assert_eq!(congelado_por_votos(&charlie_account()), 500);
assert_eq!(
crate::VoteLocks::<Test>::get(charlie_account())[0].until,
cierre
);
System::assert_last_event(RuntimeEvent::Governance(Event::OverrideVoteCast {
id,
voter: charlie_account(),
uphold: true,
weight: 500,
}));
assert_noop!(
Governance::override_council_veto(charlie_origin(), id, 1),
Error::<Test>::AlreadyOverrideVoted
);
assert_noop!(
Governance::uphold_council_veto(charlie_origin(), id, 1),
Error::<Test>::AlreadyOverrideVoted
);
// Control positivo: otra cuenta sí vota.
assert_ok!(Governance::override_council_veto(bob_origin(), id, 1));
});
}
/// El override se decide una sola vez, y solo sobre un veto.
#[test]
fn el_override_se_decide_una_sola_vez_y_solo_sobre_un_veto() {
new_test_ext().execute_with(|| {
let (id, cierre) = propuesta_vetada();
run_to_block(cierre + 1);
assert_ok!(Governance::finalize_override(bob_origin(), id));
// Decidido: no queda ventana ni quórum guardados (estado limpio, no basura).
assert!(crate::OverrideDeadline::<Test>::get(id).is_none());
assert!(crate::OverrideQuorum::<Test>::get(id).is_none());
assert_noop!(
Governance::finalize_override(bob_origin(), id),
Error::<Test>::OverrideNotPending
);
// Una propuesta rechazada por VOTO POPULAR no tiene override que decidir.
let otra = propuesta_con_periodo(12, VOTING_PERIOD);
assert_ok!(Governance::cast_vote(
charlie_origin(),
otra,
VoteStance::Nay,
10
));
run_to_block(fin_de(otra) + 1);
assert_ok!(Governance::finalize_proposal(bob_origin(), otra));
assert_noop!(
Governance::finalize_override(bob_origin(), otra),
Error::<Test>::ProposalNotVetoRejected
);
});
}
/// El veto solo cabe DURANTE el periodo de voto: antes podía llegar entre el fin
/// del periodo y el finalize, y ganaba quien llamara primero.
#[test]
fn el_veto_solo_cabe_durante_el_periodo_de_voto() {
new_test_ext().execute_with(|| {
use crate::pallet::CouncilRole;
use frame_support::dispatch::RawOrigin;
assert_ok!(Governance::add_council_member(
RawOrigin::Root.into(),
alice_account(),
CouncilRole::Chair,
));
let tarde = submit_default(alice_origin());
let a_tiempo = propuesta_con_periodo(12, VOTING_PERIOD);
run_to_block(fin_de(tarde) + 1);
assert_noop!(
Governance::veto_proposal(alice_origin(), tarde),
Error::<Test>::VotingPeriodClosed
);
// Control positivo: en el último bloque de su periodo, sí.
run_to_block(fin_de(a_tiempo));
assert_ok!(Governance::veto_proposal(alice_origin(), a_tiempo));
assert!(matches!(estado(a_tiempo), ProposalStatus::Rejected));
});
}
/// Una propuesta ORDINARIA dura al menos 14 días (PILAR_4 §6.6). La primera versión
/// de RV16 ponía 48 h, que es la vía de emergencia sin sus 5/7 endosos: cualquiera la
/// obtenía y elegía cuánto tiempo tenía el council para vetar (revisión de RV16).
#[test]
fn una_propuesta_ordinaria_dura_al_menos_14_dias() {
new_test_ext().execute_with(|| {
use crate::pallet::{
DEFAULT_VOTING_PERIOD_BLOCKS, EMERGENCY_VOTING_PERIOD_BLOCKS, MIN_VOTING_PERIOD_BLOCKS,
};
assert_eq!(MIN_VOTING_PERIOD_BLOCKS, DEFAULT_VOTING_PERIOD_BLOCKS);
assert_eq!(MIN_VOTING_PERIOD_BLOCKS, 14 * 24 * 600);
for corto in [
0u64,
EMERGENCY_VOTING_PERIOD_BLOCKS,
MIN_VOTING_PERIOD_BLOCKS - 1,
] {
assert_noop!(
Governance::submit_proposal(
alice_origin(),
TITLE_HASH,
BODY_HASH,
corto,
MIN_PROPOSAL_STAKE,
),
Error::<Test>::VotingPeriodTooShort
);
}
// Control positivo: el mínimo exacto se acepta.
assert_ok!(Governance::submit_proposal(
alice_origin(),
TITLE_HASH,
BODY_HASH,
MIN_VOTING_PERIOD_BLOCKS,
MIN_PROPOSAL_STAKE,
));
});
}
+142 -52
View File
@@ -14,7 +14,7 @@
//! procedimiento completo y el hardware al que hay que creerle).
//!
//! GENERADO POR SUBSTRATE BENCHMARK CLI VERSION 51.0.0
//! FECHA: 2026-09-24, STEPS: `50`, REPEAT: `20`
//! FECHA: 2026-10-07, STEPS: `50`, REPEAT: `20`
//! HOSTNAME: `MacBookProM3Max.local`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, DB CACHE: 1024
@@ -80,6 +80,10 @@ pub trait WeightInfo {
fn endorse_emergency_proposal() -> Weight;
/// Peso de `unlock_vote_funds` — MEDIDO sobre el WASM, no estimado a mano.
fn unlock_vote_funds() -> Weight;
/// Peso de `uphold_council_veto` — MEDIDO sobre el WASM, no estimado a mano.
fn uphold_council_veto() -> Weight;
/// Peso de `finalize_override` — MEDIDO sobre el WASM, no estimado a mano.
fn finalize_override() -> Weight;
}
/// Weights parametrizados por el `DbWeight` real del runtime.
@@ -105,8 +109,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `107`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 54_000_000 picosegundos.
Weight::from_parts(55_000_000, 0)
// Tiempo minimo de ejecucion: 44_000_000 picosegundos.
Weight::from_parts(45_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
@@ -135,8 +139,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `2320`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 46_000_000 picosegundos.
Weight::from_parts(48_000_000, 0)
// Tiempo minimo de ejecucion: 38_000_000 picosegundos.
Weight::from_parts(42_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(T::DbWeight::get().reads(10_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
@@ -161,8 +165,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `614`
// Estimado: `6196`
// Tiempo minimo de ejecucion: 61_000_000 picosegundos.
Weight::from_parts(62_000_000, 0)
// Tiempo minimo de ejecucion: 52_000_000 picosegundos.
Weight::from_parts(59_000_000, 0)
.saturating_add(Weight::from_parts(0, 6196))
.saturating_add(T::DbWeight::get().reads(9_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
@@ -183,8 +187,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `25`
// Estimado: `3554`
// Tiempo minimo de ejecucion: 12_000_000 picosegundos.
Weight::from_parts(12_000_000, 0)
// Tiempo minimo de ejecucion: 10_000_000 picosegundos.
Weight::from_parts(10_000_000, 0)
.saturating_add(Weight::from_parts(0, 3554))
.saturating_add(T::DbWeight::get().reads(6_u64))
.saturating_add(T::DbWeight::get().writes(4_u64))
@@ -205,8 +209,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `153`
// Estimado: `3554`
// Tiempo minimo de ejecucion: 13_000_000 picosegundos.
Weight::from_parts(14_000_000, 0)
// Tiempo minimo de ejecucion: 11_000_000 picosegundos.
Weight::from_parts(12_000_000, 0)
.saturating_add(Weight::from_parts(0, 3554))
.saturating_add(T::DbWeight::get().reads(6_u64))
.saturating_add(T::DbWeight::get().writes(4_u64))
@@ -215,12 +219,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `Governance::Council` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
/// Storage: `Governance::Proposals` (r:1 w:1)
/// Proof: `Governance::Proposals` (`max_values`: None, `max_size`: Some(257), added: 2732, mode: `MaxEncodedLen`)
/// Storage: `System::Number` (r:1 w:0)
/// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::CouncilSize` (r:1 w:0)
/// Proof: `Governance::CouncilSize` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoVotes` (r:6 w:1)
/// Proof: `Governance::VetoVotes` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`)
/// Storage: `System::Number` (r:1 w:0)
/// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::ProposalBond` (r:1 w:1)
/// Proof: `Governance::ProposalBond` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`)
/// Storage: `Balances::TotalIssuance` (r:1 w:0)
@@ -235,6 +239,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Governance::OverrideDeadline` (r:0 w:1)
/// Proof: `Governance::OverrideDeadline` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideQuorum` (r:0 w:1)
/// Proof: `Governance::OverrideQuorum` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoRejected` (r:0 w:1)
/// Proof: `Governance::VetoRejected` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoCount` (r:0 w:1)
@@ -243,11 +249,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `1440`
// Estimado: `16416`
// Tiempo minimo de ejecucion: 109_000_000 picosegundos.
Weight::from_parts(112_000_000, 0)
// Tiempo minimo de ejecucion: 91_000_000 picosegundos.
Weight::from_parts(95_000_000, 0)
.saturating_add(Weight::from_parts(0, 16416))
.saturating_add(T::DbWeight::get().reads(21_u64))
.saturating_add(T::DbWeight::get().writes(10_u64))
.saturating_add(T::DbWeight::get().writes(11_u64))
}
/// Storage: `Governance::AdvisorFlags` (r:1 w:1)
/// Proof: `Governance::AdvisorFlags` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
@@ -263,8 +269,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `25`
// Estimado: `3513`
// Tiempo minimo de ejecucion: 9_000_000 picosegundos.
Weight::from_parts(10_000_000, 0)
// Tiempo minimo de ejecucion: 8_000_000 picosegundos.
Weight::from_parts(9_000_000, 0)
.saturating_add(Weight::from_parts(0, 3513))
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
@@ -283,13 +289,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `98`
// Estimado: `3513`
// Tiempo minimo de ejecucion: 11_000_000 picosegundos.
Weight::from_parts(11_000_000, 0)
// Tiempo minimo de ejecucion: 10_000_000 picosegundos.
Weight::from_parts(10_000_000, 0)
.saturating_add(Weight::from_parts(0, 3513))
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `Governance::Proposals` (r:1 w:1)
/// Storage: `Governance::Proposals` (r:1 w:0)
/// Proof: `Governance::Proposals` (`max_values`: None, `max_size`: Some(257), added: 2732, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoRejected` (r:1 w:0)
/// Proof: `Governance::VetoRejected` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
@@ -297,6 +303,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `Governance::AdvisorFlags` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideVotes` (r:1 w:1)
/// Proof: `Governance::OverrideVotes` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
/// Storage: `Governance::UpholdVotes` (r:1 w:0)
/// Proof: `Governance::UpholdVotes` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideDeadline` (r:1 w:0)
/// Proof: `Governance::OverrideDeadline` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`)
/// Storage: `System::Number` (r:1 w:0)
@@ -319,11 +327,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `2468`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 56_000_000 picosegundos.
Weight::from_parts(58_000_000, 0)
// Tiempo minimo de ejecucion: 47_000_000 picosegundos.
Weight::from_parts(49_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(T::DbWeight::get().reads(13_u64))
.saturating_add(T::DbWeight::get().writes(7_u64))
.saturating_add(T::DbWeight::get().reads(14_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
}
/// Storage: `Governance::Council` (r:1 w:0)
/// Proof: `Governance::Council` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
@@ -351,8 +359,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `353`
// Estimado: `6132`
// Tiempo minimo de ejecucion: 68_000_000 picosegundos.
Weight::from_parts(69_000_000, 0)
// Tiempo minimo de ejecucion: 58_000_000 picosegundos.
Weight::from_parts(60_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(T::DbWeight::get().reads(11_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
@@ -377,8 +385,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `494`
// Estimado: `6196`
// Tiempo minimo de ejecucion: 56_000_000 picosegundos.
Weight::from_parts(58_000_000, 0)
// Tiempo minimo de ejecucion: 48_000_000 picosegundos.
Weight::from_parts(49_000_000, 0)
.saturating_add(Weight::from_parts(0, 6196))
.saturating_add(T::DbWeight::get().reads(9_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
@@ -399,8 +407,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `101`
// Estimado: `3593`
// Tiempo minimo de ejecucion: 43_000_000 picosegundos.
Weight::from_parts(43_000_000, 0)
// Tiempo minimo de ejecucion: 37_000_000 picosegundos.
Weight::from_parts(38_000_000, 0)
.saturating_add(Weight::from_parts(0, 3593))
.saturating_add(T::DbWeight::get().reads(6_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
@@ -423,8 +431,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `153`
// Estimado: `6132`
// Tiempo minimo de ejecucion: 22_000_000 picosegundos.
Weight::from_parts(23_000_000, 0)
// Tiempo minimo de ejecucion: 19_000_000 picosegundos.
Weight::from_parts(20_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
@@ -449,100 +457,180 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `2197`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 34_000_000 picosegundos.
Weight::from_parts(40_000_000, 0)
// Tiempo minimo de ejecucion: 29_000_000 picosegundos.
Weight::from_parts(30_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
}
/// Storage: `Governance::Proposals` (r:1 w:0)
/// Proof: `Governance::Proposals` (`max_values`: None, `max_size`: Some(257), added: 2732, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoRejected` (r:1 w:0)
/// Proof: `Governance::VetoRejected` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
/// Storage: `Governance::AdvisorFlags` (r:1 w:0)
/// Proof: `Governance::AdvisorFlags` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideVotes` (r:1 w:0)
/// Proof: `Governance::OverrideVotes` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
/// Storage: `Governance::UpholdVotes` (r:1 w:1)
/// Proof: `Governance::UpholdVotes` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideDeadline` (r:1 w:0)
/// Proof: `Governance::OverrideDeadline` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`)
/// Storage: `System::Number` (r:1 w:0)
/// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::VoteLocks` (r:1 w:1)
/// Proof: `Governance::VoteLocks` (`max_values`: None, `max_size`: Some(1873), added: 4348, mode: `MaxEncodedLen`)
/// Storage: `Balances::Freezes` (r:1 w:1)
/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
/// Storage: `Balances::Locks` (r:1 w:0)
/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
/// Storage: `System::ExecutionPhase` (r:1 w:0)
/// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `System::EventCount` (r:1 w:1)
/// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `System::Events` (r:1 w:1)
/// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Governance::UpholdStakeTotal` (r:1 w:1)
/// Proof: `Governance::UpholdStakeTotal` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
fn uphold_council_veto() -> Weight {
// Proof size en bytes:
// Medido: `2468`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 47_000_000 picosegundos.
Weight::from_parts(50_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(T::DbWeight::get().reads(14_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
}
/// Storage: `Governance::Proposals` (r:1 w:1)
/// Proof: `Governance::Proposals` (`max_values`: None, `max_size`: Some(257), added: 2732, mode: `MaxEncodedLen`)
/// Storage: `Governance::VetoRejected` (r:1 w:0)
/// Proof: `Governance::VetoRejected` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideDeadline` (r:1 w:1)
/// Proof: `Governance::OverrideDeadline` (`max_values`: None, `max_size`: Some(56), added: 2531, mode: `MaxEncodedLen`)
/// Storage: `System::Number` (r:1 w:0)
/// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideStakeTotal` (r:1 w:0)
/// Proof: `Governance::OverrideStakeTotal` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
/// Storage: `Governance::UpholdStakeTotal` (r:1 w:0)
/// Proof: `Governance::UpholdStakeTotal` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
/// Storage: `Governance::OverrideQuorum` (r:1 w:1)
/// Proof: `Governance::OverrideQuorum` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
/// Storage: `System::ExecutionPhase` (r:1 w:0)
/// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`)
/// Storage: `System::EventCount` (r:1 w:1)
/// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `System::Events` (r:1 w:1)
/// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn finalize_override() -> Weight {
// Proof size en bytes:
// Medido: `636`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 25_000_000 picosegundos.
Weight::from_parts(26_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(T::DbWeight::get().reads(10_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
}
}
/// Impl para mocks y tests — `RocksDbWeight` directo, sin necesitar `T`.
impl WeightInfo for () {
fn submit_proposal() -> Weight {
Weight::from_parts(55_000_000, 0)
Weight::from_parts(45_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(RocksDbWeight::get().reads(7_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
fn cast_vote() -> Weight {
Weight::from_parts(48_000_000, 0)
Weight::from_parts(42_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(10_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
}
fn finalize_proposal() -> Weight {
Weight::from_parts(62_000_000, 0)
Weight::from_parts(59_000_000, 0)
.saturating_add(Weight::from_parts(0, 6196))
.saturating_add(RocksDbWeight::get().reads(9_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
}
fn add_council_member() -> Weight {
Weight::from_parts(12_000_000, 0)
Weight::from_parts(10_000_000, 0)
.saturating_add(Weight::from_parts(0, 3554))
.saturating_add(RocksDbWeight::get().reads(6_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn remove_council_member() -> Weight {
Weight::from_parts(14_000_000, 0)
Weight::from_parts(12_000_000, 0)
.saturating_add(Weight::from_parts(0, 3554))
.saturating_add(RocksDbWeight::get().reads(6_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn veto_proposal() -> Weight {
Weight::from_parts(112_000_000, 0)
Weight::from_parts(95_000_000, 0)
.saturating_add(Weight::from_parts(0, 16416))
.saturating_add(RocksDbWeight::get().reads(21_u64))
.saturating_add(RocksDbWeight::get().writes(10_u64))
.saturating_add(RocksDbWeight::get().writes(11_u64))
}
fn mark_as_advisor() -> Weight {
Weight::from_parts(10_000_000, 0)
Weight::from_parts(9_000_000, 0)
.saturating_add(Weight::from_parts(0, 3513))
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn unmark_as_advisor() -> Weight {
Weight::from_parts(11_000_000, 0)
Weight::from_parts(10_000_000, 0)
.saturating_add(Weight::from_parts(0, 3513))
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn override_council_veto() -> Weight {
Weight::from_parts(58_000_000, 0)
Weight::from_parts(49_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(13_u64))
.saturating_add(RocksDbWeight::get().writes(7_u64))
.saturating_add(RocksDbWeight::get().reads(14_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
}
fn submit_emergency_proposal() -> Weight {
Weight::from_parts(69_000_000, 0)
Weight::from_parts(60_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(RocksDbWeight::get().reads(11_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
}
fn treasury_disbursement() -> Weight {
Weight::from_parts(58_000_000, 0)
Weight::from_parts(49_000_000, 0)
.saturating_add(Weight::from_parts(0, 6196))
.saturating_add(RocksDbWeight::get().reads(9_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
fn treasury_deposit() -> Weight {
Weight::from_parts(43_000_000, 0)
Weight::from_parts(38_000_000, 0)
.saturating_add(Weight::from_parts(0, 3593))
.saturating_add(RocksDbWeight::get().reads(6_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn endorse_emergency_proposal() -> Weight {
Weight::from_parts(23_000_000, 0)
Weight::from_parts(20_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn unlock_vote_funds() -> Weight {
Weight::from_parts(40_000_000, 0)
Weight::from_parts(30_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
fn uphold_council_veto() -> Weight {
Weight::from_parts(50_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(14_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
}
fn finalize_override() -> Weight {
Weight::from_parts(26_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(RocksDbWeight::get().reads(10_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
}
#[cfg(test)]
@@ -584,6 +672,8 @@ mod guard {
("treasury_deposit", <() as WeightInfo>::treasury_deposit()),
("endorse_emergency_proposal", <() as WeightInfo>::endorse_emergency_proposal()),
("unlock_vote_funds", <() as WeightInfo>::unlock_vote_funds()),
("uphold_council_veto", <() as WeightInfo>::uphold_council_veto()),
("finalize_override", <() as WeightInfo>::finalize_override()),
];
for (nombre, w) in casos {
assert!(