feat(governance): RV15 — el peso de un voto sale del saldo, y la revision adversaria tumbo la primera version

Cierra L-10. cast_vote y override_council_veto sumaban el stake_weight que
DECLARABA el votante (hueco medido por Palace el 2026-09-06; test en rojo
publicado en d37a278). Ahora:

- El peso no puede superar el OGC LIBRE de la cuenta, y ese OGC queda
  congelado (fungible freeze, FreezeReason::GovernanceVote) mientras el voto
  pueda cambiar un resultado: hasta end_block en un voto normal y hasta el
  cierre de la ventana en un override. El mismo OGC puede respaldar votos en
  propuestas distintas (se solapan, como en Polkadot), pero no puede salir de
  la cuenta: se cierra votar, transferir y votar otra vez desde otra cuenta.
- Un respaldo POR VOTO (VoteBacking: propuesta, tipo, importe, hasta), no uno
  por cuenta. El freeze es siempre el maximo de los votos VIVOS; los vencidos
  se descartan solos al votar o desbloquear. MaxActiveVotes = 32 en el runtime.
- unlock_vote_funds(who) lo puede llamar CUALQUIERA, como el unlock de
  conviction-voting: quien vota con todo su saldo no puede pagar la comision de
  su propio desbloqueo. Libera solo lo que ya no respalda un voto vivo.
- El override tiene ventana: 14 dias desde el veto que ALCANZA el umbral
  (antes estaba abierto para siempre). Sin ventana registrada, falla cerrado.
- voting_period_blocks tiene tope: 28 dias (MAX_VOTING_PERIOD_BLOCKS).
- VoteFundsUnlocked va al FINAL del enum de eventos: no desplaza indices SCALE.
- spec_version 14 -> 15; transaction_version sigue en 1.

La revision adversaria (4 lentes independientes sobre la primera version)
encontro tres fallos serios, y los tres eran mios:
1. El lock unico por cuenta (max importe, max bloque) mas un periodo de voto
   sin tope congelaba OGC PARA SIEMPRE: un voto de 1 unidad en una propuesta de
   periodo u64::MAX arrastraba el voto mayor de la cuenta hasta ese bloque, y ni
   el veto lo liberaba. Lo demostraron tres lentes por separado. Cura: respaldo
   por voto + tope de periodo.
2. Un lock vencido y no desbloqueado revivia con el siguiente voto pequeno.
3. Votar con todo el saldo dejaba la cuenta sin poder pagar la comision del
   unlock, y solo ella podia desbloquearse. Cura: unlock por terceros.
Y seis mutaciones sobrevivian a mi suite, entre ellas congelar el peso del
voto actual en vez del maximo (el propio ataque que el test decia cubrir).

Certificacion: 17 contrafactuales, TODOS en rojo con su pieza quitada
(freeze = peso actual, sin exigir saldo, reducible_balance, total_balance,
unlock en until, sin podar vencidos, ventana desde start_block, ventana 0,
peso 0 con thaw, evento released=0, unlock todo o nada, sin tope de periodo,
unlock solo propio, override hasta end_block, veto sin ventana, sin freeze, y
MaxFreezes=0 en el runtime). Cada test lleva control positivo. Test nuevo con
la configuracion REAL del runtime (preset local_testnet): el voto congela en
pallet-balances, lo congelado no sale, y un tercero lo libera al vencer.

Benchmarks en el PEOR caso real (la revision mostro que median la rama
barata): lista de respaldo casi llena, override que alcanza el umbral, veto de
un council de 7 que devuelve el bond, finalize que devuelve el bond, unlock
con la mitad vencida. Pesos regenerados con frame-omni-bencher (Mac; los
canonicos siguen pendientes del Pi 5, L-6). veto_proposal estaba
subdeclarado ~3x: 36M ps 10r/7w -> 112M 21r/10w, proof 6132 -> 16416.

regen-weights.sh: el blob de bench (runtime-benchmarks) se construye en
target-bench/ DENTRO del repo, nunca en target/release/wbuild, que es de donde
sale un set_code. Lo que debe coincidir con lo desplegado es el commit, no el
blob. Dentro del repo porque substrate-wasm-builder busca el Cargo.lock
subiendo desde el target: fuera no lo encuentra y resuelve dependencias
nuevas (medido).

Fuera de RV15, a RV16 (L-11): el override no es un 4/5 real (el bond que el
veto devuelve y el OGC Aye cuentan en el denominador y en el numerador), el
veto sigue siendo posible tras end_block, y un periodo de voto minimo.
Existian antes de RV15; los demostro la misma revision.

cargo test --workspace: 334 passed, 0 failed, 0 ignored.
cargo test -p origin-chain-governance --features runtime-benchmarks: 79 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CharonandClaude Opus 5.5 committed 2026-09-24 12:03:44 -05:00
1 parent d37a278ba6
commit a694cbd9f0
10 files changed
+1097 -106

No files matched your search

@@ -15,6 +15,7 @@
use super::*;
use frame_benchmarking::v2::*;
use frame_support::traits::fungible::{Inspect, Mutate};
use frame_support::traits::Get;
use frame_system::RawOrigin;
use origin_chain_types::AccountId;
@@ -52,6 +53,34 @@ fn seed_council<T: Config>(who: &T::AccountId) {
CouncilSize::<T>::put(1u32);
}
/// RV15: siembra `n` votos vivos respaldados por `who` (propuestas distintas del
/// id de los benchmarks), con `until` dado. Es el peor caso de PoV: la lista de
/// respaldo se lee y se reescribe entera.
fn seed_backing<T: Config>(who: &T::AccountId, n: u32, until: u64) {
for i in 0..n {
Pallet::<T>::back_vote_with_funds(
who,
ProposalId([100u8.wrapping_add(i as u8); 32]),
BackingKind::Vote,
1_000u128.saturating_add(i as u128),
until,
)
.expect("respaldo sembrado");
}
}
/// Bond real de la propuesta `id`, con fondos en la cuenta-bond: la rama cara
/// del veto y del finalize es la que lo devuelve.
fn seed_bond<T: Config>(id: ProposalId) -> T::AccountId {
let proposer: T::AccountId = account("proposer", 0, 0);
let _ = T::Currency::mint_into(
&Pallet::<T>::proposal_bond_account(),
MIN_PROPOSAL_STAKE.saturating_mul(2),
);
ProposalBond::<T>::insert(id, (proposer.clone(), MIN_PROPOSAL_STAKE));
proposer
}
const EM_TITLE: [u8; 32] = [3u8; 32];
const EM_BODY: [u8; 32] = [4u8; 32];
@@ -73,6 +102,14 @@ mod benchmarks {
#[benchmark]
fn cast_vote() {
let caller: T::AccountId = whitelisted_caller();
// RV15: el peso sale del saldo. Peor caso: la cuenta ya respalda
// MaxActiveVotes - 1 votos vivos (lista y freeze que leer y reescribir).
fund::<T>(&caller);
seed_backing::<T>(
&caller,
T::MaxActiveVotes::get().saturating_sub(1),
1_000_000u64,
);
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 1_000_000u64);
@@ -87,6 +124,8 @@ mod benchmarks {
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 0u64);
// Rama cara: devuelve el bond (transfer real desde la cuenta-bond).
let proposer = seed_bond::<T>(id);
// current_block debe ser > end_block(0) → avanza a 2.
frame_system::Pallet::<T>::set_block_number(2u32.into());
@@ -97,6 +136,8 @@ mod benchmarks {
Proposals::<T>::get(id).unwrap().status,
ProposalStatus::Voting
));
assert!(!ProposalBond::<T>::contains_key(id));
assert_eq!(T::Currency::balance(&proposer), MIN_PROPOSAL_STAKE);
}
#[benchmark]
@@ -122,15 +163,43 @@ mod benchmarks {
#[benchmark]
fn veto_proposal() {
// Rama cara: council LLENO (7), 4 vetos vivos previos, y el del caller es el
// 5.º que alcanza el umbral 2/3 → recorre todos los vetos, flipa a Rejected,
// abre la ventana de override (RV15) y devuelve el bond.
let caller: T::AccountId = whitelisted_caller();
seed_council::<T>(&caller);
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 1_000_000u64);
let proposer = seed_bond::<T>(id);
let mut miembros: alloc::vec::Vec<T::AccountId> = (0..MAX_COUNCIL_SIZE - 1)
.map(|i| account("council", i, 0))
.collect();
miembros.push(caller.clone());
for m in &miembros {
Council::<T>::insert(
m,
CouncilMember {
account: AccountId::new([0u8; 32]),
joined_at_block: 0,
role: CouncilRole::Member,
},
);
}
CouncilSize::<T>::put(MAX_COUNCIL_SIZE);
let previos = council_veto_threshold(MAX_COUNCIL_SIZE) - 1;
for m in miembros.iter().take(previos as usize) {
VetoVotes::<T>::insert(id, m, ());
}
#[extrinsic_call]
veto_proposal(RawOrigin::Signed(caller.clone()), id);
assert!(VetoVotes::<T>::contains_key(id, &caller));
assert!(matches!(
Proposals::<T>::get(id).unwrap().status,
ProposalStatus::Rejected
));
assert!(OverrideDeadline::<T>::contains_key(id));
assert_eq!(T::Currency::balance(&proposer), MIN_PROPOSAL_STAKE);
}
#[benchmark]
@@ -160,11 +229,24 @@ mod benchmarks {
let id = ProposalId([7u8; 32]);
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.
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]
override_council_veto(RawOrigin::Signed(caller.clone()), id, 100u128);
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]
@@ -222,5 +304,33 @@ mod benchmarks {
assert!(EmergencyEndorsements::<T>::contains_key(content_id, &caller));
}
#[benchmark]
fn unlock_vote_funds() {
// Peor caso: lista de respaldo LLENA, la mitad vencida (until = 1) y la otra
// mitad viva → se poda, se recalcula el máximo y se reescribe la lista.
let caller: T::AccountId = whitelisted_caller();
let votante: T::AccountId = account("votante", 0, 0);
fund::<T>(&votante);
let max = T::MaxActiveVotes::get();
let vencidos = max / 2;
seed_backing::<T>(&votante, vencidos, 1u64);
for i in vencidos..max {
Pallet::<T>::back_vote_with_funds(
&votante,
ProposalId([100u8.wrapping_add(i as u8); 32]),
BackingKind::Vote,
500u128,
1_000_000u64,
)
.expect("respaldo vivo");
}
frame_system::Pallet::<T>::set_block_number(2u32.into());
#[extrinsic_call]
unlock_vote_funds(RawOrigin::Signed(caller), votante.clone());
assert_eq!(VoteLocks::<T>::get(&votante).len() as u32, max - vencidos);
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+256 -2
View File
@@ -124,7 +124,7 @@ sp_api::decl_runtime_apis! {
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_support::traits::fungible::{Inspect, Mutate};
use frame_support::traits::fungible::{freeze, Inspect, Mutate};
use frame_support::traits::tokens::Preservation;
use frame_support::PalletId;
use frame_system::pallet_prelude::*;
@@ -192,6 +192,22 @@ pub mod pallet {
/// 14 days × 24 hours × 600 blocks/hour (@ 6s slot Aura) = 201,600 blocks.
pub const DEFAULT_VOTING_PERIOD_BLOCKS: u64 = 14 * 24 * 600;
/// RV15: ventana para revertir un veto del council, contada desde el bloque en
/// que el veto alcanza su umbral. Mide lo mismo que un voto normal (14 días).
///
/// Antes no había ventana: una propuesta vetada aceptaba votos de override para
/// siempre, así que podía resucitarse años después. Y con peso real (RV15) el
/// OGC que respalda un override tiene que liberarse ALGÚN día: sin ventana, o se
/// congelaba para siempre o el voto dejaba de estar respaldado.
pub const OVERRIDE_WINDOW_BLOCKS: u64 = DEFAULT_VOTING_PERIOD_BLOCKS;
/// RV15: periodo de voto máximo que puede pedir un proponente (28 días).
///
/// Antes era libre hasta `u64::MAX`. Con el voto respaldado por OGC eso era un
/// arma: una propuesta de periodo casi eterno congelaba casi para siempre el OGC
/// 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;
/// 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;
@@ -208,6 +224,35 @@ pub mod pallet {
(total_stake.saturating_mul(4).saturating_add(4)) / 5
}
/// RV15: qué respalda un tramo de OGC congelado.
#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
pub enum BackingKind {
/// Un voto del periodo normal (`cast_vote`).
Vote,
/// Un voto para revertir un veto (`override_council_veto`).
Override,
}
/// RV15: el OGC que respalda UN voto vivo.
///
/// Un registro por voto, no uno por cuenta. La primera versión fusionaba todo
/// en un único lock (máximo importe hasta el bloque más tardío) y la revisión
/// adversaria demostró por qué no sirve: un voto mínimo en una propuesta de
/// periodo enorme arrastraba el voto MAYOR de la cuenta hasta ese bloque, para
/// siempre, y un lock vencido revivía con el siguiente voto. Con un registro por
/// voto, cada tramo vence solo y el freeze es siempre el MÁXIMO DE LOS VIVOS.
#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
pub struct VoteBacking {
/// Propuesta a la que respalda.
pub proposal: ProposalId,
/// Voto normal u override.
pub kind: BackingKind,
/// OGC que respalda este voto.
pub amount: u128,
/// Último bloque (inclusive) en que este voto aún puede cambiar el resultado.
pub until: u64,
}
/// Proposal canonical struct stored on-chain per pallet (Foundation OIP).
#[derive(
Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq,
@@ -248,7 +293,21 @@ pub mod pallet {
/// FIX-6 (V2-CA2/V3-C3): Currency real (fungible) para mover OGC de la
/// tesorería. La tesorería deja de ser un contador u128 y pasa a ser una
/// cuenta real respaldada por pallet_balances.
type Currency: Inspect<Self::AccountId, Balance = u128> + Mutate<Self::AccountId>;
///
/// RV15: además congela (`freeze`) el OGC que respalda un voto, para que el
/// mismo OGC no pueda votar dos veces pasando por una transferencia.
type Currency: Inspect<Self::AccountId, Balance = u128>
+ Mutate<Self::AccountId>
+ freeze::Mutate<Self::AccountId, Id = Self::RuntimeFreezeReason>;
/// RV15: el enum agregado de razones de congelación del runtime.
type RuntimeFreezeReason: From<FreezeReason>;
/// RV15: cuántos votos VIVOS puede respaldar una cuenta a la vez. Los
/// vencidos se descartan solos al votar o desbloquear, así que el límite es
/// de votos simultáneos, no históricos.
#[pallet::constant]
type MaxActiveVotes: Get<u32>;
/// 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
@@ -271,6 +330,33 @@ pub mod pallet {
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
/// RV15: por qué este pallet congela OGC.
#[pallet::composite_enum]
pub enum FreezeReason {
/// OGC que respalda un voto (o un override) que todavía puede cambiar un
/// resultado.
#[codec(index = 0)]
GovernanceVote,
}
/// 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]
pub type VoteLocks<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<VoteBacking, T::MaxActiveVotes>,
ValueQuery,
>;
/// RV15: último bloque (inclusive) en que se acepta un override sobre una
/// propuesta vetada. Se escribe cuando el veto alcanza su umbral. Sin entrada,
/// no hay override posible: falla cerrado.
#[pallet::storage]
pub type OverrideDeadline<T: Config> =
StorageMap<_, Blake2_128Concat, ProposalId, u64, OptionQuery>;
/// CHN-4 canonical: `Proposals<StorageMap<ProposalId, Proposal>>` canonical.
#[pallet::storage]
#[pallet::getter(fn proposal_of)]
@@ -557,6 +643,17 @@ pub mod pallet {
/// Treasury balance canonical post-disbursement.
remaining_balance: u128,
},
/// RV15: se liberó OGC que respaldaba votos que ya no pueden cambiar un
/// resultado. Va AL FINAL del enum a propósito: insertarlo en medio desplazaba
/// el índice SCALE de tres eventos existentes (revisión adversaria de RV15).
VoteFundsUnlocked {
/// Cuenta cuyo OGC se libera.
who: T::AccountId,
/// OGC que deja de estar congelado.
released: u128,
/// OGC que sigue congelado por votos todavía vivos.
still_frozen: u128,
},
}
/// Pallet errors canonical.
@@ -616,6 +713,22 @@ pub mod pallet {
/// Audit 2026-07-04 (B): el council member ya endosó esta propuesta de
/// emergencia (anti doble-endoso).
AlreadyEndorsed,
/// RV15: el peso del voto supera el OGC de la cuenta. El peso sale del
/// saldo, no de lo que declara el votante (hueco medido por Palace el
/// 2026-09-06).
VoteWeightExceedsBalance,
/// RV15: la ventana para revertir este veto ya cerró (o la propuesta no
/// tiene ventana registrada).
OverrideWindowClosed,
/// RV15: la cuenta no tiene OGC congelado por votos.
NoVoteLock,
/// RV15: ninguno de los votos de la cuenta ha vencido todavía; su OGC sigue
/// congelado.
VoteFundsStillLocked,
/// RV15: el periodo de voto pedido supera `MAX_VOTING_PERIOD_BLOCKS`.
VotingPeriodTooLong,
/// RV15: la cuenta ya respalda `MaxActiveVotes` votos vivos.
TooManyActiveVotes,
}
/// Minimum stake canonical proposal submission per CHN-4 5.3.
@@ -646,6 +759,10 @@ pub mod pallet {
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(stake >= MIN_PROPOSAL_STAKE, Error::<T>::StakeInsufficient);
ensure!(
voting_period_blocks <= MAX_VOTING_PERIOD_BLOCKS,
Error::<T>::VotingPeriodTooLong
);
let proposer_acct = AccountId::new(account_bytes::<T>(&who));
let id = crate::derive_proposal_id(&proposer_acct, &title_hash, &body_hash);
@@ -731,6 +848,17 @@ pub mod pallet {
Error::<T>::AlreadyVoted
);
// RV15: el peso sale del saldo y queda congelado hasta que el voto ya no
// pueda cambiar el resultado (fin del periodo de voto). Sin el freeze,
// el mismo OGC votaría otra vez desde otra cuenta tras una transferencia.
Self::back_vote_with_funds(
&who,
proposal_id,
BackingKind::Vote,
stake_weight,
proposal.end_block,
)?;
match stance {
VoteStance::Aye => {
proposal.yes_weight = proposal.yes_weight.saturating_add(stake_weight)
@@ -948,6 +1076,11 @@ pub mod pallet {
// Marca: rechazado por VETO del council (no por voto popular) →
// única vía elegible para override_council_veto.
VetoRejected::<T>::insert(proposal_id, ());
// RV15: el override tiene ventana, contada desde este bloque.
OverrideDeadline::<T>::insert(
proposal_id,
current_block_u64::<T>().saturating_add(OVERRIDE_WINDOW_BLOCKS),
);
// Audit 2026-07-04: la propuesta salió de Voting (vetada) → devuelve
// el bond al proponente (si no, el stake de una propuesta vetada
// quedaría atrapado para siempre — no hay withdraw dispatchable).
@@ -1060,6 +1193,22 @@ pub mod pallet {
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);
@@ -1296,6 +1445,111 @@ pub mod pallet {
});
Ok(())
}
/// RV15: libera el OGC que respaldaba votos de `who` que ya no pueden cambiar
/// un resultado (pasó su `until`). Lo que respalda votos todavía vivos sigue
/// congelado.
///
/// Lo puede llamar CUALQUIERA, como `unlock` de `pallet-conviction-voting`: la
/// condición es objetiva, y quien votó con todo su saldo no puede pagar la
/// comisión de su propio desbloqueo (revisión adversaria de RV15).
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::unlock_vote_funds())]
pub fn unlock_vote_funds(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
ensure_signed(origin)?;
let mut backing = VoteLocks::<T>::get(&who);
ensure!(!backing.is_empty(), Error::<T>::NoVoteLock);
let before = max_backing(&backing);
let now = current_block_u64::<T>();
let n = backing.len();
backing.retain(|b| b.until >= now);
ensure!(backing.len() < n, Error::<T>::VoteFundsStillLocked);
let after = max_backing(&backing);
Self::apply_vote_freeze(&who, backing, after)?;
Self::deposit_event(Event::VoteFundsUnlocked {
who,
released: before.saturating_sub(after),
still_frozen: after,
});
Ok(())
}
}
/// El mayor OGC respaldado por una lista de votos (0 si está vacía).
fn max_backing(backing: &[VoteBacking]) -> u128 {
backing.iter().map(|b| b.amount).max().unwrap_or(0)
}
/// Bloque actual como `u64` (el tipo de `end_block` y de las ventanas).
fn current_block_u64<T: Config>() -> u64 {
<frame_system::Pallet<T>>::block_number().saturated_into::<u64>()
}
/// 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
/// mientras este voto pueda cambiar un resultado (hasta `until`).
///
/// Antes descarta los respaldos ya vencidos de la cuenta, así que un voto
/// muerto nunca vuelve a congelarse. El freeze resultante es el MÁXIMO de los
/// votos vivos: el mismo OGC puede respaldar votos en propuestas distintas
/// (se solapan, como en Polkadot), pero no puede salir de la cuenta mientras
/// un voto que respalda siga vivo. Eso cierra votar, transferir y votar otra
/// vez desde otra cuenta.
///
/// Un peso 0 no congela nada ni toca el respaldo que ya hubiera.
///
/// Límite conocido: el freeze de `pallet-balances` se cumple sobre libre +
/// retenido. Si la cuenta tiene OGC en hold (staking), lo retenido puede
/// sostener el freeze mientras lo libre sale: en la práctica el OGC en hold
/// termina respaldando el voto, que es la semántica de Polkadot. No infla el
/// total: ninguna cuenta respalda más de lo que posee.
pub(crate) fn back_vote_with_funds(
who: &T::AccountId,
proposal: ProposalId,
kind: BackingKind,
weight: u128,
until: u64,
) -> DispatchResult {
ensure!(
weight <= T::Currency::balance(who),
Error::<T>::VoteWeightExceedsBalance
);
if weight == 0 {
return Ok(());
}
let now = current_block_u64::<T>();
let mut backing = VoteLocks::<T>::get(who);
backing.retain(|b| b.until >= now);
backing
.try_push(VoteBacking {
proposal,
kind,
amount: weight,
until,
})
.map_err(|_| Error::<T>::TooManyActiveVotes)?;
let frozen = max_backing(&backing);
Self::apply_vote_freeze(who, backing, frozen)
}
/// Deja el freeze `GovernanceVote` de `who` en `frozen` y guarda su respaldo;
/// sin votos vivos, descongela y borra el registro.
fn apply_vote_freeze(
who: &T::AccountId,
backing: BoundedVec<VoteBacking, T::MaxActiveVotes>,
frozen: u128,
) -> DispatchResult {
let reason = FreezeReason::GovernanceVote.into();
if backing.is_empty() {
<T::Currency as freeze::Mutate<T::AccountId>>::thaw(&reason, who)?;
VoteLocks::<T>::remove(who);
} else {
<T::Currency as freeze::Mutate<T::AccountId>>::set_freeze(&reason, who, frozen)?;
VoteLocks::<T>::insert(who, backing);
}
Ok(())
}
}
/// FIX-6 — helpers de la tesorería real (cuenta respaldada por pallet_balances).
@@ -31,6 +31,9 @@ impl frame_system::Config for Test {
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = u128;
// RV15: governance congela el OGC que respalda un voto.
type FreezeIdentifier = RuntimeFreezeReason;
type MaxFreezes = frame_support::traits::VariantCountOf<RuntimeFreezeReason>;
}
frame_support::parameter_types! {
@@ -42,6 +45,9 @@ frame_support::parameter_types! {
impl origin_chain_governance::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type RuntimeFreezeReason = RuntimeFreezeReason;
// Bajo a propósito: el límite de votos vivos tiene que poder probarse.
type MaxActiveVotes = frame_support::traits::ConstU32<4>;
type TreasuryPalletId = TestTreasuryPalletId;
type WeightInfo = ();
}
@@ -17,7 +17,8 @@
use crate::mock::{
alice_account, alice_origin, bob_account, bob_origin, charlie_account, charlie_origin,
new_test_ext, run_to_block, Balances, Governance, RuntimeEvent, System, Test, INITIAL_BALANCE,
new_test_ext, run_to_block, Balances, Governance, RuntimeEvent, RuntimeFreezeReason,
RuntimeOrigin, System, Test, INITIAL_BALANCE,
};
use crate::pallet::{Proposal, ProposalStatus, MIN_PROPOSAL_STAKE};
use crate::{derive_proposal_id, Error, Event, Proposals, Votes};
@@ -1145,19 +1146,12 @@ fn scenario_10_lifecycle_walkthrough_canonical() {
});
}
/// HUECO ABIERTO — medido por Palace el 2026-09-06, confirmado por Charon el
/// 2026-09-23 sobre `63918f3`. `cast_vote` (y `override_council_veto`) suman el
/// `stake_weight` que DECLARA el votante sin contrastarlo con nada: el diseño dice
/// «1 OGC = 1 voto» (doc de `lib.rs:403`) y el código es un contador que suma lo que
/// le digan. Hoy no drena la tesorería porque `treasury_disbursement` exige además
/// `ensure_root`, pero cualquiera aprueba o bloquea una propuesta con `u128::MAX`,
/// y el día que se retire el sudo (L-7) y la gobernanza ejecute (L-8) sí la drena.
///
/// El `ignore` NO lo esconde: `cargo test -- --ignored` lo enseña en rojo, y
/// QUITARLO es el criterio de cierre de RV15 (peso derivado del saldo + un freeze
/// para que el mismo OGC no vote dos veces pasando por una transferencia).
/// CERRADO en RV15. Hueco medido por Palace el 2026-09-06 y confirmado por Charon el
/// 2026-09-23 sobre `63918f3`: `cast_vote` (y `override_council_veto`) sumaban el
/// `stake_weight` que DECLARA el votante sin contrastarlo con nada. Este test estuvo
/// publicado en rojo (`d37a278`, con `#[ignore]`) antes de la cura; quitar el
/// `ignore` era el criterio de cierre.
#[test]
#[ignore = "RV15: cast_vote acepta el peso que declara el votante (hueco medido 2026-09-06)"]
fn un_voto_no_puede_pesar_mas_que_el_saldo_del_votante() {
new_test_ext().execute_with(|| {
let id = submit_default(alice_origin());
@@ -1184,3 +1178,471 @@ fn un_voto_no_puede_pesar_mas_que_el_saldo_del_votante() {
assert_eq!(p.yes_weight, saldo, "un voto legítimo no contó entero");
});
}
// ═══════════════════════════════════════════════════════════════
// RV15 — el peso de un voto sale del saldo y ese OGC queda congelado mientras
// el voto pueda cambiar un resultado. Cada test lleva su control positivo, y
// cada uno mata al menos una mutación que la revisión adversaria de RV15 vio
// sobrevivir a la primera versión de esta suite.
// ═══════════════════════════════════════════════════════════════
/// OGC congelado HOY por votos de gobernanza (lo que mide `pallet-balances`, no
/// el registro del pallet: la revisión mostró que mirar solo el registro deja
/// vivo un freeze equivocado).
fn congelado_por_votos(who: &sp_runtime::AccountId32) -> u128 {
use frame_support::traits::fungible::InspectFreeze;
let razon: RuntimeFreezeReason = crate::FreezeReason::GovernanceVote.into();
Balances::balance_frozen(&razon, who)
}
/// Otra propuesta de Alice, con su propio periodo.
fn propuesta_con_periodo(semilla: u8, periodo: u64) -> ProposalId {
let (t, b) = ([semilla; 32], [semilla.wrapping_add(100); 32]);
assert_ok!(Governance::submit_proposal(
alice_origin(),
t,
b,
periodo,
MIN_PROPOSAL_STAKE,
));
derive_proposal_id(&AccountId::new(alice_account_bytes()), &t, &b)
}
fn fin_de(id: ProposalId) -> u64 {
Proposals::<Test>::get(id).expect("propuesta").end_block
}
/// El mismo OGC no puede votar dos veces pasando por una transferencia: el que
/// respalda un voto vivo no sale de la cuenta.
#[test]
fn el_ogc_que_respalda_un_voto_no_sale_de_la_cuenta_mientras_el_voto_vive() {
new_test_ext().execute_with(|| {
let id = submit_default(alice_origin());
let saldo = Balances::free_balance(bob_account());
let peso = saldo / 2;
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Aye,
peso
));
assert_eq!(congelado_por_votos(&bob_account()), peso);
let r = Balances::transfer_allow_death(bob_origin(), charlie_account(), saldo - peso + 1);
assert!(
r.is_err(),
"salió de la cuenta OGC congelado por un voto vivo"
);
assert_eq!(Balances::free_balance(bob_account()), saldo);
// Control positivo: lo que NO respalda el voto se mueve libre.
assert_ok!(Balances::transfer_allow_death(
bob_origin(),
charlie_account(),
saldo - peso
));
assert_eq!(Balances::free_balance(bob_account()), peso);
});
}
/// Con varios votos vivos, el freeze REAL es el mayor, no el último. Primero la
/// propuesta larga con más peso, luego la corta con menos: congelar el peso del
/// voto actual dejaría 300 y liberaría 200 que respaldan un voto vivo.
#[test]
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 saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::cast_vote(
bob_origin(),
larga,
VoteStance::Nay,
500
));
assert_ok!(Governance::cast_vote(
bob_origin(),
corta,
VoteStance::Aye,
300
));
assert_eq!(congelado_por_votos(&bob_account()), 500);
// El ataque con dinero real: sacar los 200 que respaldan el voto largo.
assert!(
Balances::transfer_allow_death(bob_origin(), charlie_account(), saldo - 300).is_err()
);
// Control positivo: lo no respaldado sí sale.
assert_ok!(Balances::transfer_allow_death(
bob_origin(),
charlie_account(),
saldo - 500
));
// Acabada la corta, el voto largo sigue vivo: nada que liberar.
run_to_block(fin_de(corta) + 1);
assert_ok!(Governance::unlock_vote_funds(bob_origin(), bob_account()));
assert_eq!(congelado_por_votos(&bob_account()), 500);
});
}
/// Al vencer el voto grande, se libera lo que ya no respalda nada y sigue
/// congelado lo que respalda el voto pequeño todavía vivo.
#[test]
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);
assert_ok!(Governance::cast_vote(
bob_origin(),
larga,
VoteStance::Aye,
300
));
assert_ok!(Governance::cast_vote(
bob_origin(),
corta,
VoteStance::Aye,
500
));
assert_eq!(congelado_por_votos(&bob_account()), 500);
run_to_block(fin_de(corta));
assert_noop!(
Governance::unlock_vote_funds(bob_origin(), bob_account()),
Error::<Test>::VoteFundsStillLocked
);
run_to_block(fin_de(corta) + 1);
assert_ok!(Governance::unlock_vote_funds(bob_origin(), bob_account()));
assert_eq!(congelado_por_votos(&bob_account()), 300);
System::assert_last_event(RuntimeEvent::Governance(Event::VoteFundsUnlocked {
who: bob_account(),
released: 200,
still_frozen: 300,
}));
run_to_block(fin_de(larga) + 1);
assert_ok!(Governance::unlock_vote_funds(bob_origin(), bob_account()));
assert_eq!(congelado_por_votos(&bob_account()), 0);
assert!(crate::VoteLocks::<Test>::get(bob_account()).is_empty());
System::assert_last_event(RuntimeEvent::Governance(Event::VoteFundsUnlocked {
who: bob_account(),
released: 300,
still_frozen: 0,
}));
});
}
/// El OGC se libera cuando el voto ya no puede cambiar el resultado: en el último
/// bloque del periodo todavía se puede votar, así que sigue congelado.
#[test]
fn el_ogc_se_libera_solo_cuando_el_voto_ya_no_puede_cambiar_el_resultado() {
new_test_ext().execute_with(|| {
let id = submit_default(alice_origin());
let saldo = Balances::free_balance(bob_account());
assert_noop!(
Governance::unlock_vote_funds(bob_origin(), bob_account()),
Error::<Test>::NoVoteLock
);
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Nay,
saldo
));
run_to_block(fin_de(id));
assert_noop!(
Governance::unlock_vote_funds(bob_origin(), bob_account()),
Error::<Test>::VoteFundsStillLocked
);
run_to_block(fin_de(id) + 1);
assert_ok!(Governance::unlock_vote_funds(bob_origin(), bob_account()));
assert_eq!(congelado_por_votos(&bob_account()), 0);
// Control positivo: liberado, el OGC se mueve entero.
assert_ok!(Balances::transfer_allow_death(
bob_origin(),
charlie_account(),
saldo
));
});
}
/// Quien votó con todo su saldo no puede pagar la comisión de su propio
/// desbloqueo; por eso cualquiera puede desbloquear un voto vencido ajeno.
#[test]
fn un_tercero_libera_el_voto_vencido_de_otra_cuenta() {
new_test_ext().execute_with(|| {
let id = submit_default(alice_origin());
let saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Aye,
saldo
));
// Control negativo: el tercero no puede liberar un voto vivo.
assert_noop!(
Governance::unlock_vote_funds(charlie_origin(), bob_account()),
Error::<Test>::VoteFundsStillLocked
);
run_to_block(fin_de(id) + 1);
assert_ok!(Governance::unlock_vote_funds(
charlie_origin(),
bob_account()
));
assert_eq!(congelado_por_votos(&bob_account()), 0);
});
}
/// Un proponente no puede pedir un periodo casi eterno: con el voto respaldado
/// por OGC, eso congelaría casi para siempre el OGC de quien votara.
#[test]
fn una_propuesta_no_puede_pedir_un_periodo_de_voto_eterno() {
new_test_ext().execute_with(|| {
use crate::pallet::{DEFAULT_VOTING_PERIOD_BLOCKS, MAX_VOTING_PERIOD_BLOCKS};
assert_eq!(MAX_VOTING_PERIOD_BLOCKS, 2 * DEFAULT_VOTING_PERIOD_BLOCKS);
for eterno in [u64::MAX, MAX_VOTING_PERIOD_BLOCKS + 1] {
assert_noop!(
Governance::submit_proposal(
alice_origin(),
TITLE_HASH,
BODY_HASH,
eterno,
MIN_PROPOSAL_STAKE,
),
Error::<Test>::VotingPeriodTooLong
);
}
// Control positivo: el máximo exacto se acepta.
assert_ok!(Governance::submit_proposal(
alice_origin(),
TITLE_HASH,
BODY_HASH,
MAX_VOTING_PERIOD_BLOCKS,
MIN_PROPOSAL_STAKE,
));
});
}
/// Un voto vencido que nadie desbloqueó no revive con el siguiente voto.
#[test]
fn un_voto_vencido_no_revive_con_el_siguiente() {
new_test_ext().execute_with(|| {
let corta = submit_default(alice_origin());
assert_ok!(Governance::cast_vote(
bob_origin(),
corta,
VoteStance::Aye,
500
));
run_to_block(fin_de(corta) + 1);
let nueva = propuesta_con_periodo(12, VOTING_PERIOD);
assert_ok!(Governance::cast_vote(
bob_origin(),
nueva,
VoteStance::Aye,
1
));
assert_eq!(congelado_por_votos(&bob_account()), 1);
let respaldo = crate::VoteLocks::<Test>::get(bob_account());
assert_eq!(respaldo.len(), 1);
assert_eq!(respaldo[0].proposal, nueva);
});
}
/// El mismo OGC, ENTERO, vota en dos propuestas (se solapan, como en Polkadot);
/// lo que no puede es pesar más que el saldo.
#[test]
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 saldo = Balances::free_balance(bob_account());
assert_ok!(Governance::cast_vote(
bob_origin(),
p1,
VoteStance::Aye,
saldo
));
assert_noop!(
Governance::cast_vote(bob_origin(), p2, VoteStance::Nay, saldo + 1),
Error::<Test>::VoteWeightExceedsBalance
);
assert_ok!(Governance::cast_vote(
bob_origin(),
p2,
VoteStance::Nay,
saldo
));
assert_eq!(congelado_por_votos(&bob_account()), saldo);
assert_eq!(Proposals::<Test>::get(p1).expect("p1").yes_weight, saldo);
assert_eq!(Proposals::<Test>::get(p2).expect("p2").nay_weight, saldo);
});
}
/// Vota el OGC LIBRE: lo que está en reserva/hold no suma peso al votar.
#[test]
fn lo_que_esta_en_reserva_no_vota() {
new_test_ext().execute_with(|| {
use frame_support::traits::ReservableCurrency;
let id = submit_default(alice_origin());
let saldo = Balances::free_balance(bob_account());
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(
&bob_account(),
saldo / 2
));
let libre = Balances::free_balance(bob_account());
assert_noop!(
Governance::cast_vote(bob_origin(), id, VoteStance::Aye, libre + 1),
Error::<Test>::VoteWeightExceedsBalance
);
assert_ok!(Governance::cast_vote(
bob_origin(),
id,
VoteStance::Aye,
libre
));
});
}
/// Un voto de peso 0 cuenta como postura y no toca el respaldo que ya hubiera.
#[test]
fn un_voto_de_peso_cero_no_toca_el_freeze_previo() {
new_test_ext().execute_with(|| {
let p1 = submit_default(alice_origin());
let p2 = propuesta_con_periodo(11, VOTING_PERIOD);
assert_ok!(Governance::cast_vote(
bob_origin(),
p2,
VoteStance::Abstain,
0
));
assert!(Votes::<Test>::contains_key(p2, &bob_account()));
assert!(crate::VoteLocks::<Test>::get(bob_account()).is_empty());
assert_ok!(Governance::cast_vote(
bob_origin(),
p1,
VoteStance::Aye,
500
));
let p3 = propuesta_con_periodo(12, VOTING_PERIOD);
assert_ok!(Governance::cast_vote(bob_origin(), p3, VoteStance::Nay, 0));
assert_eq!(congelado_por_votos(&bob_account()), 500);
assert_eq!(crate::VoteLocks::<Test>::get(bob_account()).len(), 1);
});
}
/// La ventana de override dura 14 días (literal, no la propia constante) y se
/// cuenta desde el veto que ALCANZA el umbral, no desde el inicio de la
/// propuesta ni desde el primer veto. Y su peso también sale del saldo.
#[test]
fn la_ventana_de_override_dura_14_dias_desde_el_veto_que_alcanza_el_umbral() {
new_test_ext().execute_with(|| {
use crate::pallet::CouncilRole;
use frame_support::dispatch::RawOrigin;
let dave = sp_runtime::AccountId32::new([4u8; 32]);
let eve = sp_runtime::AccountId32::new([5u8; 32]);
for (m, rol) in [
(alice_account(), CouncilRole::Chair),
(dave.clone(), CouncilRole::Member),
(eve.clone(), CouncilRole::Member),
] {
assert_ok!(Governance::add_council_member(
RawOrigin::Root.into(),
m,
rol
));
}
let id = submit_default(alice_origin()); // bloque 1
run_to_block(10);
assert_ok!(Governance::veto_proposal(alice_origin(), id)); // 1 de 2
assert!(crate::OverrideDeadline::<Test>::get(id).is_none());
run_to_block(60);
assert_ok!(Governance::veto_proposal(RuntimeOrigin::signed(dave), id)); // 2 de 2
let cierre = 60 + 201_600;
assert_eq!(crate::OverrideDeadline::<Test>::get(id), Some(cierre));
let saldo = Balances::free_balance(bob_account());
assert_noop!(
Governance::override_council_veto(bob_origin(), id, saldo + 1),
Error::<Test>::VoteWeightExceedsBalance
);
run_to_block(cierre + 1);
assert_noop!(
Governance::override_council_veto(bob_origin(), id, 1),
Error::<Test>::OverrideWindowClosed
);
// Control positivo: en el último bloque de la ventana sí, y congela hasta el cierre.
run_to_block(cierre);
assert_ok!(Governance::override_council_veto(bob_origin(), id, 1));
assert_eq!(congelado_por_votos(&bob_account()), 1);
let respaldo = crate::VoteLocks::<Test>::get(bob_account());
assert_eq!(respaldo[0].until, cierre);
assert_eq!(respaldo[0].kind, crate::BackingKind::Override);
});
}
/// Un veto sin ventana registrada (lo que dejaría una migración mal hecha) no
/// admite override: falla cerrado.
#[test]
fn un_veto_sin_ventana_registrada_no_admite_override() {
new_test_ext().execute_with(|| {
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::override_council_veto(bob_origin(), id, 1),
Error::<Test>::OverrideWindowClosed
);
});
}
/// Una cuenta respalda como mucho `MaxActiveVotes` votos VIVOS (4 en el mock); los
/// vencidos no ocupan sitio.
#[test]
fn el_limite_es_de_votos_vivos_y_los_vencidos_no_ocupan_sitio() {
new_test_ext().execute_with(|| {
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));
}
assert_ok!(Governance::cast_vote(
bob_origin(),
corta,
VoteStance::Aye,
10
));
for id in &largas[..3] {
assert_ok!(Governance::cast_vote(
bob_origin(),
*id,
VoteStance::Aye,
10
));
}
assert_noop!(
Governance::cast_vote(bob_origin(), largas[3], VoteStance::Aye, 10),
Error::<Test>::TooManyActiveVotes
);
// Control positivo: vencida la corta, su sitio queda libre.
run_to_block(fin_de(corta) + 1);
assert_ok!(Governance::cast_vote(
bob_origin(),
largas[3],
VoteStance::Aye,
10
));
assert_eq!(crate::VoteLocks::<Test>::get(bob_account()).len(), 4);
});
}
+148 -81
View File
@@ -14,17 +14,17 @@
//! procedimiento completo y el hardware al que hay que creerle).
//!
//! GENERADO POR SUBSTRATE BENCHMARK CLI VERSION 51.0.0
//! FECHA: 2026-09-09, STEPS: `50`, REPEAT: `20`
//! FECHA: 2026-09-24, STEPS: `50`, REPEAT: `20`
//! HOSTNAME: `MacBookProM3Max.local`, CPU: `<UNKNOWN>`
//! WASM-EXECUTION: `Compiled`, DB CACHE: 1024
// Comando ejecutado:
// /Users/davidherran/.cargo/bin/frame-omni-bencher
// frame-omni-bencher
// v1
// benchmark
// pallet
// --runtime
// /Users/davidherran/originmetaverse/artifacts/origin-chain/origin_chain_runtime_rv12_bench.compact.compressed.wasm
// /Users/davidherran/originmetaverse/external/arkhe-origin-chain/target-bench/release/wbuild/origin-chain-runtime/origin_chain_runtime.compact.compressed.wasm
// --genesis-builder-preset
// development
// --pallet
@@ -78,6 +78,8 @@ pub trait WeightInfo {
fn treasury_deposit() -> Weight;
/// Peso de `endorse_emergency_proposal` — MEDIDO sobre el WASM, no estimado a mano.
fn endorse_emergency_proposal() -> Weight;
/// Peso de `unlock_vote_funds` — MEDIDO sobre el WASM, no estimado a mano.
fn unlock_vote_funds() -> Weight;
}
/// Weights parametrizados por el `DbWeight` real del runtime.
@@ -103,8 +105,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `107`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 46_000_000 picosegundos.
Weight::from_parts(46_000_000, 0)
// Tiempo minimo de ejecucion: 54_000_000 picosegundos.
Weight::from_parts(55_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))
@@ -117,6 +119,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Governance::Votes` (r:1 w:1)
/// Proof: `Governance::Votes` (`max_values`: None, `max_size`: Some(97), added: 2572, 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)
@@ -125,20 +133,24 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn cast_vote() -> Weight {
// Proof size en bytes:
// Medido: `310`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 16_000_000 picosegundos.
Weight::from_parts(18_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes(4_u64))
// Medido: `2320`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 46_000_000 picosegundos.
Weight::from_parts(48_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))
}
/// 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::ProposalBond` (r:1 w:0)
/// 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)
/// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, 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)
@@ -147,13 +159,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn finalize_proposal() -> Weight {
// Proof size en bytes:
// Medido: `310`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 13_000_000 picosegundos.
Weight::from_parts(14_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(T::DbWeight::get().reads(6_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
// Medido: `614`
// Estimado: `6196`
// Tiempo minimo de ejecucion: 61_000_000 picosegundos.
Weight::from_parts(62_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))
}
/// Storage: `Governance::CouncilSize` (r:1 w:1)
/// Proof: `Governance::CouncilSize` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -171,8 +183,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `25`
// Estimado: `3554`
// Tiempo minimo de ejecucion: 10_000_000 picosegundos.
Weight::from_parts(11_000_000, 0)
// Tiempo minimo de ejecucion: 12_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))
@@ -193,43 +205,49 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `153`
// Estimado: `3554`
// Tiempo minimo de ejecucion: 11_000_000 picosegundos.
Weight::from_parts(12_000_000, 0)
// Tiempo minimo de ejecucion: 13_000_000 picosegundos.
Weight::from_parts(14_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))
}
/// Storage: `Governance::Council` (r:1 w:0)
/// Storage: `Governance::Council` (r:5 w:0)
/// 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: `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:2 w:1)
/// Storage: `Governance::VetoVotes` (r:6 w:1)
/// Proof: `Governance::VetoVotes` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`)
/// Storage: `Governance::ProposalBond` (r:1 w:0)
/// Proof: `Governance::ProposalBond` (`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)
/// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, 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::OverrideDeadline` (r:0 w:1)
/// Proof: `Governance::OverrideDeadline` (`max_values`: None, `max_size`: Some(56), added: 2531, 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)
/// Proof: `Governance::VetoCount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
fn veto_proposal() -> Weight {
// Proof size en bytes:
// Medido: `435`
// Estimado: `6132`
// Tiempo minimo de ejecucion: 28_000_000 picosegundos.
Weight::from_parts(30_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(T::DbWeight::get().reads(10_u64))
.saturating_add(T::DbWeight::get().writes(6_u64))
// Medido: `1440`
// Estimado: `16416`
// Tiempo minimo de ejecucion: 109_000_000 picosegundos.
Weight::from_parts(112_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))
}
/// Storage: `Governance::AdvisorFlags` (r:1 w:1)
/// Proof: `Governance::AdvisorFlags` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`)
@@ -265,13 +283,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `98`
// Estimado: `3513`
// Tiempo minimo de ejecucion: 10_000_000 picosegundos.
Weight::from_parts(10_000_000, 0)
// Tiempo minimo de ejecucion: 11_000_000 picosegundos.
Weight::from_parts(11_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:0)
/// 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`)
@@ -279,17 +297,33 @@ 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::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::OverrideStakeTotal` (r:1 w:1)
/// Proof: `Governance::OverrideStakeTotal` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
fn override_council_veto() -> Weight {
// Proof size en bytes:
// Medido: `361`
// Estimado: `3722`
// Tiempo minimo de ejecucion: 16_000_000 picosegundos.
Weight::from_parts(16_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
// Medido: `2468`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 56_000_000 picosegundos.
Weight::from_parts(58_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))
}
/// Storage: `Governance::Council` (r:1 w:0)
/// Proof: `Governance::Council` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`)
@@ -317,8 +351,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `353`
// Estimado: `6132`
// Tiempo minimo de ejecucion: 60_000_000 picosegundos.
Weight::from_parts(61_000_000, 0)
// Tiempo minimo de ejecucion: 68_000_000 picosegundos.
Weight::from_parts(69_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))
@@ -343,8 +377,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `494`
// Estimado: `6196`
// Tiempo minimo de ejecucion: 51_000_000 picosegundos.
Weight::from_parts(52_000_000, 0)
// Tiempo minimo de ejecucion: 56_000_000 picosegundos.
Weight::from_parts(58_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))
@@ -365,8 +399,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `101`
// Estimado: `3593`
// Tiempo minimo de ejecucion: 38_000_000 picosegundos.
Weight::from_parts(42_000_000, 0)
// Tiempo minimo de ejecucion: 43_000_000 picosegundos.
Weight::from_parts(43_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))
@@ -389,51 +423,77 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof size en bytes:
// Medido: `153`
// Estimado: `6132`
// Tiempo minimo de ejecucion: 20_000_000 picosegundos.
Weight::from_parts(21_000_000, 0)
// Tiempo minimo de ejecucion: 22_000_000 picosegundos.
Weight::from_parts(23_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))
}
/// Storage: `Governance::VoteLocks` (r:1 w:1)
/// Proof: `Governance::VoteLocks` (`max_values`: None, `max_size`: Some(1873), added: 4348, 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: `Balances::Freezes` (r:1 w:1)
/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, 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`)
fn unlock_vote_funds() -> Weight {
// Proof size en bytes:
// Medido: `2197`
// Estimado: `5338`
// Tiempo minimo de ejecucion: 34_000_000 picosegundos.
Weight::from_parts(40_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))
}
}
/// Impl para mocks y tests — `RocksDbWeight` directo, sin necesitar `T`.
impl WeightInfo for () {
fn submit_proposal() -> Weight {
Weight::from_parts(46_000_000, 0)
Weight::from_parts(55_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(18_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(RocksDbWeight::get().reads(7_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
Weight::from_parts(48_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(14_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(RocksDbWeight::get().reads(6_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
Weight::from_parts(62_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(11_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(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 remove_council_member() -> Weight {
Weight::from_parts(14_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(30_000_000, 0)
.saturating_add(Weight::from_parts(0, 6132))
.saturating_add(RocksDbWeight::get().reads(10_u64))
.saturating_add(RocksDbWeight::get().writes(6_u64))
Weight::from_parts(112_000_000, 0)
.saturating_add(Weight::from_parts(0, 16416))
.saturating_add(RocksDbWeight::get().reads(21_u64))
.saturating_add(RocksDbWeight::get().writes(10_u64))
}
fn mark_as_advisor() -> Weight {
Weight::from_parts(10_000_000, 0)
@@ -442,41 +502,47 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
fn unmark_as_advisor() -> Weight {
Weight::from_parts(10_000_000, 0)
Weight::from_parts(11_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(16_000_000, 0)
.saturating_add(Weight::from_parts(0, 3722))
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
Weight::from_parts(58_000_000, 0)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(13_u64))
.saturating_add(RocksDbWeight::get().writes(7_u64))
}
fn submit_emergency_proposal() -> Weight {
Weight::from_parts(61_000_000, 0)
Weight::from_parts(69_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(52_000_000, 0)
Weight::from_parts(58_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(42_000_000, 0)
Weight::from_parts(43_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(21_000_000, 0)
Weight::from_parts(23_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)
.saturating_add(Weight::from_parts(0, 5338))
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
}
#[cfg(test)]
@@ -517,6 +583,7 @@ mod guard {
("treasury_disbursement", <() as WeightInfo>::treasury_disbursement()),
("treasury_deposit", <() as WeightInfo>::treasury_deposit()),
("endorse_emergency_proposal", <() as WeightInfo>::endorse_emergency_proposal()),
("unlock_vote_funds", <() as WeightInfo>::unlock_vote_funds()),
];
for (nombre, w) in casos {
assert!(
@@ -531,6 +531,11 @@ parameter_types! {
impl origin_chain_governance::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = crate::Balances;
// RV15: el OGC que respalda un voto queda congelado (freeze) mientras el voto
// pueda cambiar un resultado. `MaxFreezes` de Balances cuenta sola la variante.
type RuntimeFreezeReason = RuntimeFreezeReason;
// RV15: votos VIVOS simultáneos por cuenta (los vencidos se descartan solos).
type MaxActiveVotes = frame_support::traits::ConstU32<32>;
type TreasuryPalletId = GovernanceTreasuryPalletId;
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
type WeightInfo = origin_chain_governance::weights::SubstrateWeight<Runtime>;
@@ -604,4 +604,76 @@ mod tests {
"el pool og/ecosy debe tener exactamente los 400M que la emisión libera"
);
}
/// RV15 con la configuración REAL del runtime (no el mock): un voto congela su
/// OGC en `pallet-balances`, lo congelado no sale, y vencido el voto un TERCERO
/// lo libera. Sin este test, un cambio de configuración (por ejemplo
/// `MaxFreezes = 0`) haría fallar en producción todo voto con peso sin que
/// ningún test del pallet lo viera (revisión adversaria de RV15).
#[test]
fn un_voto_congela_ogc_con_la_configuracion_real_del_runtime() {
use crate::{Balances, Governance, RuntimeFreezeReason, RuntimeOrigin, System};
use frame_support::assert_ok;
use frame_support::traits::fungible::{Inspect, InspectFreeze};
use origin_chain_governance::{FreezeReason, MIN_PROPOSAL_STAKE};
use origin_chain_types::VoteStance;
let json = get_preset(&PresetId::from(PRESET_LOCAL_TESTNET)).expect("preset");
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
frame_support::genesis_builder_helper::build_state::<RuntimeGenesisConfig>(json)
.expect("el preset construye estado");
System::set_block_number(1);
let alice = account_from_sr25519(ALICE_SR25519);
let bob = account_from_sr25519(BOB_SR25519);
let charlie = account_from_sr25519(CHARLIE_SR25519);
let (titulo, cuerpo, periodo) = ([1u8; 32], [2u8; 32], 100u64);
assert_ok!(Governance::submit_proposal(
RuntimeOrigin::signed(alice.clone()),
titulo,
cuerpo,
periodo,
MIN_PROPOSAL_STAKE,
));
let id = origin_chain_governance::derive_proposal_id(
&origin_chain_types::AccountId::new(ALICE_SR25519),
&titulo,
&cuerpo,
);
let saldo = Balances::balance(&bob);
assert!(saldo > 0, "el preset local debe fondear a Bob");
assert_ok!(Governance::cast_vote(
RuntimeOrigin::signed(bob.clone()),
id,
VoteStance::Aye,
saldo,
));
let razon: RuntimeFreezeReason = FreezeReason::GovernanceVote.into();
assert_eq!(Balances::balance_frozen(&razon, &bob), saldo);
assert!(
Balances::transfer_allow_death(
RuntimeOrigin::signed(bob.clone()),
charlie.clone().into(),
1,
)
.is_err(),
"salió OGC congelado por un voto vivo"
);
System::set_block_number(1 + periodo as u32 + 1);
assert_ok!(Governance::unlock_vote_funds(
RuntimeOrigin::signed(charlie.clone()),
bob.clone(),
));
assert_eq!(Balances::balance_frozen(&razon, &bob), 0);
// Control positivo: liberado, el OGC vuelve a moverse.
assert_ok!(Balances::transfer_allow_death(
RuntimeOrigin::signed(bob),
charlie.into(),
1,
));
});
}
}
+2 -2
View File
@@ -1019,7 +1019,7 @@ pub const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed("origin-chain"),
impl_name: alloc::borrow::Cow::Borrowed("origin-chain-charon"),
authoring_version: 1,
spec_version: 14,
spec_version: 15,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
@@ -1087,7 +1087,7 @@ mod tests {
/// mismo commit en que la version subio a 12.
#[test]
fn spec_version_vigente_y_transaction_version_intacta() {
assert_eq!(RUNTIME_VERSION, 14);
assert_eq!(RUNTIME_VERSION, 15);
assert_eq!(TRANSACTION_VERSION, 1);
}