//! # Benchmarks para `pallet-origin-governance` (FRAME v2) //! //! Fuente de verdad de la forma del weight de los 13 extrinsics. Verificado por //! `impl_benchmark_test_suite!` en el mock (`cargo test -p origin-chain-governance //! --features runtime-benchmarks`). Números canónicos gated por HW/omni-bencher //! (ver `weights.rs` §Estado). //! //! Los preconditions (proposal en un estado, membresía de council, endosos) se //! siembran por storage directo en el setup — patrón estándar de benchmarking. //! Los thresholds con council_size=1 son 1 (veto y emergency), así que un solo //! miembro/endoso alcanza el umbral. #![cfg(feature = "runtime-benchmarks")] 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; /// Fondea `who` con holgura para bonds + treasury deposits. fn fund(who: &T::AccountId) { let _ = T::Currency::mint_into(who, MIN_PROPOSAL_STAKE.saturating_mul(100)); } /// Siembra una `Proposal` en el estado/end_block dados (bypass del extrinsic). fn seed_proposal(id: ProposalId, status: ProposalStatus, end_block: u64) { let proposal = Proposal { id, proposer: AccountId::new([9u8; 32]), title_hash: [1u8; 32], body_hash: [2u8; 32], stake: MIN_PROPOSAL_STAKE, start_block: 0, end_block, status, yes_weight: 0, nay_weight: 0, abstain_weight: 0, }; Proposals::::insert(id, proposal); } /// Añade `who` al council (size=1) por storage directo. fn seed_council(who: &T::AccountId) { let member = CouncilMember { account: AccountId::new([0u8; 32]), joined_at_block: 0, role: CouncilRole::Member, }; Council::::insert(who, member); CouncilSize::::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(who: &T::AccountId, n: u32, until: u64) { for i in 0..n { Pallet::::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(id: ProposalId) -> T::AccountId { let proposer: T::AccountId = account("proposer", 0, 0); let _ = T::Currency::mint_into( &Pallet::::proposal_bond_account(), MIN_PROPOSAL_STAKE.saturating_mul(2), ); ProposalBond::::insert(id, (proposer.clone(), MIN_PROPOSAL_STAKE)); proposer } const EM_TITLE: [u8; 32] = [3u8; 32]; const EM_BODY: [u8; 32] = [4u8; 32]; #[benchmarks] mod benchmarks { use super::*; #[benchmark] fn submit_proposal() { let caller: T::AccountId = whitelisted_caller(); fund::(&caller); #[extrinsic_call] submit_proposal(RawOrigin::Signed(caller), [1u8; 32], [2u8; 32], 1_000u64, MIN_PROPOSAL_STAKE); assert_eq!(Proposals::::iter().count(), 1); } #[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::(&caller); seed_backing::( &caller, T::MaxActiveVotes::get().saturating_sub(1), 1_000_000u64, ); let id = ProposalId([7u8; 32]); seed_proposal::(id, ProposalStatus::Voting, 1_000_000u64); #[extrinsic_call] cast_vote(RawOrigin::Signed(caller.clone()), id, VoteStance::Aye, 100u128); assert!(Votes::::contains_key(id, &caller)); } #[benchmark] fn finalize_proposal() { let caller: T::AccountId = whitelisted_caller(); let id = ProposalId([7u8; 32]); seed_proposal::(id, ProposalStatus::Voting, 0u64); // Rama cara: devuelve el bond (transfer real desde la cuenta-bond). let proposer = seed_bond::(id); // current_block debe ser > end_block(0) → avanza a 2. frame_system::Pallet::::set_block_number(2u32.into()); #[extrinsic_call] finalize_proposal(RawOrigin::Signed(caller), id); assert!(!matches!( Proposals::::get(id).unwrap().status, ProposalStatus::Voting )); assert!(!ProposalBond::::contains_key(id)); assert_eq!(T::Currency::balance(&proposer), MIN_PROPOSAL_STAKE); } #[benchmark] fn add_council_member() { let acct: T::AccountId = account("member", 0, 0); #[extrinsic_call] add_council_member(RawOrigin::Root, acct.clone(), CouncilRole::Member); assert!(Council::::contains_key(&acct)); } #[benchmark] fn remove_council_member() { let acct: T::AccountId = account("member", 0, 0); seed_council::(&acct); #[extrinsic_call] remove_council_member(RawOrigin::Root, acct.clone()); assert!(!Council::::contains_key(&acct)); } #[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(); let id = ProposalId([7u8; 32]); seed_proposal::(id, ProposalStatus::Voting, 1_000_000u64); let proposer = seed_bond::(id); let mut miembros: alloc::vec::Vec = (0..MAX_COUNCIL_SIZE - 1) .map(|i| account("council", i, 0)) .collect(); miembros.push(caller.clone()); for m in &miembros { Council::::insert( m, CouncilMember { account: AccountId::new([0u8; 32]), joined_at_block: 0, role: CouncilRole::Member, }, ); } CouncilSize::::put(MAX_COUNCIL_SIZE); let previos = council_veto_threshold(MAX_COUNCIL_SIZE) - 1; for m in miembros.iter().take(previos as usize) { VetoVotes::::insert(id, m, ()); } #[extrinsic_call] veto_proposal(RawOrigin::Signed(caller.clone()), id); assert!(VetoVotes::::contains_key(id, &caller)); assert!(matches!( Proposals::::get(id).unwrap().status, ProposalStatus::Rejected )); assert!(OverrideDeadline::::contains_key(id)); assert_eq!(T::Currency::balance(&proposer), MIN_PROPOSAL_STAKE); } #[benchmark] fn mark_as_advisor() { let acct: T::AccountId = account("advisor", 0, 0); #[extrinsic_call] mark_as_advisor(RawOrigin::Root, acct.clone()); assert!(AdvisorFlags::::contains_key(&acct)); } #[benchmark] fn unmark_as_advisor() { let acct: T::AccountId = account("advisor", 0, 0); AdvisorFlags::::insert(&acct, ()); #[extrinsic_call] unmark_as_advisor(RawOrigin::Root, acct.clone()); assert!(!AdvisorFlags::::contains_key(&acct)); } #[benchmark] fn override_council_veto() { let caller: T::AccountId = whitelisted_caller(); let id = ProposalId([7u8; 32]); seed_proposal::(id, ProposalStatus::Rejected, 0u64); VetoRejected::::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::::insert(id, 1_000_000u64); fund::(&caller); seed_backing::( &caller, T::MaxActiveVotes::get().saturating_sub(1), 1_000_000u64, ); #[extrinsic_call] override_council_veto(RawOrigin::Signed(caller.clone()), id, MIN_PROPOSAL_STAKE); assert!(OverrideVotes::::contains_key(id, &caller)); assert!(matches!( Proposals::::get(id).unwrap().status, ProposalStatus::Approved )); } #[benchmark] fn submit_emergency_proposal() { let caller: T::AccountId = whitelisted_caller(); fund::(&caller); seed_council::(&caller); // council_size=1 ⇒ emergency threshold=1; un endoso VIVO del caller basta. let content_id = crate::emergency_content_id(&EM_TITLE, &EM_BODY); EmergencyEndorsements::::insert(content_id, &caller, ()); #[extrinsic_call] submit_emergency_proposal(RawOrigin::Signed(caller), EM_TITLE, EM_BODY, MIN_PROPOSAL_STAKE); assert!(Proposals::::iter().count() >= 1); } #[benchmark] fn treasury_disbursement() { let id = ProposalId([7u8; 32]); seed_proposal::(id, ProposalStatus::Approved, 0u64); let recipient: T::AccountId = account("recipient", 0, 0); // Fondea la tesorería para que el transfer real tenga de dónde salir. let _ = T::Currency::mint_into( &Pallet::::treasury_account(), MIN_PROPOSAL_STAKE.saturating_mul(10), ); #[extrinsic_call] treasury_disbursement(RawOrigin::Root, id, recipient, MIN_PROPOSAL_STAKE); assert!(Disbursed::::contains_key(id)); } #[benchmark] fn treasury_deposit() { let caller: T::AccountId = whitelisted_caller(); fund::(&caller); #[extrinsic_call] treasury_deposit(RawOrigin::Signed(caller), MIN_PROPOSAL_STAKE); assert!(T::Currency::balance(&Pallet::::treasury_account()) >= MIN_PROPOSAL_STAKE); } #[benchmark] fn endorse_emergency_proposal() { let caller: T::AccountId = whitelisted_caller(); seed_council::(&caller); #[extrinsic_call] endorse_emergency_proposal(RawOrigin::Signed(caller.clone()), EM_TITLE, EM_BODY); let content_id = crate::emergency_content_id(&EM_TITLE, &EM_BODY); assert!(EmergencyEndorsements::::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::(&votante); let max = T::MaxActiveVotes::get(); let vencidos = max / 2; seed_backing::(&votante, vencidos, 1u64); for i in vencidos..max { Pallet::::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::::set_block_number(2u32.into()); #[extrinsic_call] unlock_vote_funds(RawOrigin::Signed(caller), votante.clone()); assert_eq!(VoteLocks::::get(&votante).len() as u32, max - vencidos); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); }