// This file is part of Substrate. // Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Democracy pallet benchmarking. use super::*; use crate::Pallet as Referenda; use assert_matches::assert_matches; use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::{ assert_ok, traits::{Currency, EnsureOrigin}, }; use frame_system::RawOrigin; use sp_runtime::traits::{Bounded, Hash}; const SEED: u32 = 0; #[allow(dead_code)] fn assert_last_event(generic_event: ::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); } fn funded_account(name: &'static str, index: u32) -> T::AccountId { let caller: T::AccountId = account(name, index, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); caller } fn create_referendum() -> (T::AccountId, ReferendumIndex) { let caller = funded_account::("caller", 0); whitelist_account!(caller); assert_ok!(Referenda::::submit( RawOrigin::Signed(caller.clone()).into(), Box::new(RawOrigin::Root.into()), T::Hashing::hash_of(&0), DispatchTime::After(0u32.into()) )); let index = ReferendumCount::::get() - 1; (caller, index) } fn place_deposit(index: ReferendumIndex) { let caller = funded_account::("caller", 0); whitelist_account!(caller); assert_ok!(Referenda::::place_decision_deposit(RawOrigin::Signed(caller).into(), index)); } fn nudge(index: ReferendumIndex) { assert_ok!(Referenda::::nudge_referendum(RawOrigin::Root.into(), index)); } fn fill_queue( index: ReferendumIndex, spaces: u32, pass_after: u32, ) -> Vec { // First, create enough other referendums to fill the track. let mut others = vec![]; for _ in 0..info::(index).max_deciding { let (_caller, index) = create_referendum::(); place_deposit::(index); others.push(index); } // We will also need enough referenda which are queued and passing, we want `MaxQueued - 1` // in order to force the maximum amount of work to insert ours into the queue. for _ in spaces..T::MaxQueued::get() { let (_caller, index) = create_referendum::(); place_deposit::(index); make_passing_after::(index, Perbill::from_percent(pass_after)); others.push(index); } // Skip to when they can start being decided. skip_prepare_period::(index); // Manually nudge the other referenda first to ensure that they begin. others.iter().for_each(|&i| nudge::(i)); others } fn info(index: ReferendumIndex) -> &'static TrackInfoOf { let status = Referenda::::ensure_ongoing(index).unwrap(); T::Tracks::info(status.track).expect("Id value returned from T::Tracks") } fn make_passing_after(index: ReferendumIndex, period_portion: Perbill) { let support = info::(index).min_support.threshold(period_portion); let approval = info::(index).min_approval.threshold(period_portion); Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally, class) = status { *tally = T::Tally::from_requirements(support, approval, class); } }); } fn make_passing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally, class) = status { *tally = T::Tally::unanimity(class); } }); } fn make_failing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { if let PollStatus::Ongoing(tally, class) = status { *tally = T::Tally::rejection(class); } }); } fn skip_prepare_period(index: ReferendumIndex) { let status = Referenda::::ensure_ongoing(index).unwrap(); let prepare_period_over = status.submitted + info::(index).prepare_period; frame_system::Pallet::::set_block_number(prepare_period_over); } fn skip_decision_period(index: ReferendumIndex) { let status = Referenda::::ensure_ongoing(index).unwrap(); let decision_period_over = status.deciding.unwrap().since + info::(index).decision_period; frame_system::Pallet::::set_block_number(decision_period_over); } fn skip_confirm_period(index: ReferendumIndex) { let status = Referenda::::ensure_ongoing(index).unwrap(); let confirm_period_over = status.deciding.unwrap().confirming.unwrap(); frame_system::Pallet::::set_block_number(confirm_period_over); } fn skip_timeout_period(index: ReferendumIndex) { let status = Referenda::::ensure_ongoing(index).unwrap(); let timeout_period_over = status.submitted + T::UndecidingTimeout::get(); frame_system::Pallet::::set_block_number(timeout_period_over); } fn alarm_time(index: ReferendumIndex) -> T::BlockNumber { let status = Referenda::::ensure_ongoing(index).unwrap(); status.alarm.unwrap().0 } fn is_confirming(index: ReferendumIndex) -> bool { let status = Referenda::::ensure_ongoing(index).unwrap(); matches!( status, ReferendumStatus { deciding: Some(DecidingStatus { confirming: Some(_), .. }), .. } ) } fn is_not_confirming(index: ReferendumIndex) -> bool { let status = Referenda::::ensure_ongoing(index).unwrap(); matches!( status, ReferendumStatus { deciding: Some(DecidingStatus { confirming: None, .. }), .. } ) } benchmarks! { submit { let caller = funded_account::("caller", 0); whitelist_account!(caller); }: _( RawOrigin::Signed(caller), Box::new(RawOrigin::Root.into()), T::Hashing::hash_of(&0), DispatchTime::After(0u32.into()) ) verify { let index = ReferendumCount::::get().checked_sub(1).unwrap(); assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Ongoing(_))); } place_decision_deposit_preparing { let (caller, index) = create_referendum::(); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { assert!(Referenda::::ensure_ongoing(index).unwrap().decision_deposit.is_some()); } place_decision_deposit_queued { let (caller, index) = create_referendum::(); fill_queue::(index, 1, 90); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } place_decision_deposit_not_queued { let (caller, index) = create_referendum::(); fill_queue::(index, 0, 90); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); } place_decision_deposit_passing { let (caller, index) = create_referendum::(); skip_prepare_period::(index); make_passing::(index); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { assert!(is_confirming::(index)); } place_decision_deposit_failing { let (caller, index) = create_referendum::(); skip_prepare_period::(index); }: place_decision_deposit(RawOrigin::Signed(caller), index) verify { assert!(is_not_confirming::(index)); } refund_decision_deposit { let (caller, index) = create_referendum::(); place_deposit::(index); assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); }: _(RawOrigin::Signed(caller), index) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); } cancel { let (_caller, index) = create_referendum::(); place_deposit::(index); }: _(T::CancelOrigin::successful_origin(), index) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(..))); } kill { let (_caller, index) = create_referendum::(); place_deposit::(index); }: _(T::KillOrigin::successful_origin(), index) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Killed(..))); } one_fewer_deciding_queue_empty { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); assert_eq!(DecidingCount::::get(&track), 1); }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), 0); } one_fewer_deciding_failing { let (_caller, index) = create_referendum::(); // No spaces free in the queue. let queued = fill_queue::(index, 0, 90); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), deciding_count); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); assert!(queued.into_iter().skip(1).all(|i| Referenda::::ensure_ongoing(i) .unwrap() .deciding .map_or(true, |d| d.confirming.is_none()) )); } one_fewer_deciding_passing { let (_caller, index) = create_referendum::(); // No spaces free in the queue. let queued = fill_queue::(index, 0, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), deciding_count); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); assert!(queued.into_iter().skip(1).all(|i| Referenda::::ensure_ongoing(i) .unwrap() .deciding .map_or(true, |d| d.confirming.is_some()) )); } nudge_referendum_requeued_insertion { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); fill_queue::(index, 0, 90); // Now nudge ours, with the track now full and the queue full of referenda with votes, // ours will not be in the queue. nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); // Now alter the voting, so that ours goes into pole-position and shifts others down. make_passing::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let t = TrackQueue::::get(&track); assert_eq!(t.len() as u32, T::MaxQueued::get()); assert_eq!(t[t.len() - 1].0, index); } nudge_referendum_requeued_slide { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); fill_queue::(index, 1, 90); // Now nudge ours, with the track now full, ours will be queued, but with no votes, it // will have the worst position. nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); // Now alter the voting, so that ours leap-frogs all into the best position. make_passing::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let t = TrackQueue::::get(&track); assert_eq!(t.len() as u32, T::MaxQueued::get()); assert_eq!(t[t.len() - 1].0, index); } nudge_referendum_queued { // NOTE: worst possible queue situation is with a queue full of passing refs with one slot // free and this failing. It would result in `QUEUE_SIZE - 1` items being shifted for the // insertion at the beginning. // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); fill_queue::(index, 1, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); assert!(TrackQueue::::get(&track).into_iter().all(|(_, v)| v > 0u32.into())); // Then nudge ours, with the track now full, ours will be queued. }: nudge_referendum(RawOrigin::Root, index) verify { assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert_eq!(TrackQueue::::get(&track)[0], (index, 0u32.into())); } nudge_referendum_not_queued { // First create our referendum and place the deposit. It will be failing. let (_caller, index) = create_referendum::(); place_deposit::(index); fill_queue::(index, 0, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert!(TrackQueue::::get(&track).into_iter().all(|(_, v)| v > 0u32.into())); // Then nudge ours, with the track now full, ours will be queued. }: nudge_referendum(RawOrigin::Root, index) verify { assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); } nudge_referendum_no_deposit { let (_caller, index) = create_referendum::(); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let status = Referenda::::ensure_ongoing(index).unwrap(); assert_matches!(status, ReferendumStatus { deciding: None, .. }); } nudge_referendum_preparing { let (_caller, index) = create_referendum::(); place_deposit::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let status = Referenda::::ensure_ongoing(index).unwrap(); assert_matches!(status, ReferendumStatus { deciding: None, .. }); } nudge_referendum_timed_out { let (_caller, index) = create_referendum::(); skip_timeout_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let info = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(info, ReferendumInfo::TimedOut(..)); } nudge_referendum_begin_deciding_failing { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { assert!(is_not_confirming::(index)); } nudge_referendum_begin_deciding_passing { let (_caller, index) = create_referendum::(); place_deposit::(index); make_passing::(index); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { assert!(is_confirming::(index)); } nudge_referendum_begin_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); nudge::(index); assert!(!is_confirming::(index)); make_passing::(index); }: nudge_referendum(RawOrigin::Root, index) verify { assert!(is_confirming::(index)); } nudge_referendum_end_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); make_passing::(index); nudge::(index); assert!(is_confirming::(index)); make_failing::(index); }: nudge_referendum(RawOrigin::Root, index) verify { assert!(!is_confirming::(index)); } nudge_referendum_continue_not_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); nudge::(index); assert!(!is_confirming::(index)); let old_alarm = alarm_time::(index); make_passing_after::(index, Perbill::from_percent(50)); }: nudge_referendum(RawOrigin::Root, index) verify { assert_ne!(old_alarm, alarm_time::(index)); assert!(!is_confirming::(index)); } nudge_referendum_continue_confirming { let (_caller, index) = create_referendum::(); place_deposit::(index); make_passing::(index); skip_prepare_period::(index); nudge::(index); assert!(is_confirming::(index)); let old_alarm = alarm_time::(index); }: nudge_referendum(RawOrigin::Root, index) verify { assert!(is_confirming::(index)); } nudge_referendum_approved { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); make_passing::(index); nudge::(index); skip_confirm_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let info = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(info, ReferendumInfo::Approved(..)); } nudge_referendum_rejected { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); make_failing::(index); nudge::(index); skip_decision_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { let info = ReferendumInfoFor::::get(index).unwrap(); assert_matches!(info, ReferendumInfo::Rejected(..)); } impl_benchmark_test_suite!( Referenda, crate::mock::new_test_ext(), crate::mock::Test ); }