#![cfg(feature = "runtime-benchmarks")]
use frame_benchmarking::v2::*;
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize};
use frame_system::RawOrigin;
#[cfg(test)]
use crate::tests::MEMBERS;
use crate::*;
const BALANCE_FACTOR: u32 = 250;
fn endowed_account<T: Config>(name: &'static str, index: u32) -> T::AccountId {
let account: T::AccountId = account(name, index, 0);
let amount = default_stake::<T>(T::MaxVoters::get()) * BalanceOf::<T>::from(BALANCE_FACTOR);
let _ = T::Currency::make_free_balance_be(&account, amount);
let _ = T::Currency::issue(amount);
account
}
fn as_lookup<T: Config>(account: T::AccountId) -> AccountIdLookupOf<T> {
T::Lookup::unlookup(account)
}
fn default_stake<T: Config>(num_votes: u32) -> BalanceOf<T> {
let min = T::Currency::minimum_balance();
Pallet::<T>::deposit_of(num_votes as usize).max(min)
}
fn candidate_count<T: Config>() -> u32 {
Candidates::<T>::decode_len().unwrap_or(0usize) as u32
}
fn submit_candidates<T: Config>(
c: u32,
prefix: &'static str,
) -> Result<Vec<T::AccountId>, &'static str> {
(0..c)
.map(|i| {
let account = endowed_account::<T>(prefix, i);
Pallet::<T>::submit_candidacy(
RawOrigin::Signed(account.clone()).into(),
candidate_count::<T>(),
)
.map_err(|_| "failed to submit candidacy")?;
Ok(account)
})
.collect::<Result<_, _>>()
}
fn submit_candidates_with_self_vote<T: Config>(
c: u32,
prefix: &'static str,
) -> Result<Vec<T::AccountId>, &'static str> {
let candidates = submit_candidates::<T>(c, prefix)?;
let stake = default_stake::<T>(c);
let _ = candidates
.iter()
.try_for_each(|c| submit_voter::<T>(c.clone(), vec![c.clone()], stake).map(|_| ()))?;
Ok(candidates)
}
fn submit_voter<T: Config>(
caller: T::AccountId,
votes: Vec<T::AccountId>,
stake: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
Pallet::<T>::vote(RawOrigin::Signed(caller).into(), votes, stake)
}
fn distribute_voters<T: Config>(
mut all_candidates: Vec<T::AccountId>,
num_voters: u32,
votes: usize,
) -> Result<(), &'static str> {
let stake = default_stake::<T>(num_voters);
for i in 0..num_voters {
all_candidates.rotate_left(1);
let votes = all_candidates.iter().cloned().take(votes).collect::<Vec<_>>();
let voter = endowed_account::<T>("voter", i);
submit_voter::<T>(voter, votes, stake)?;
}
Ok(())
}
fn fill_seats_up_to<T: Config>(m: u32) -> Result<Vec<T::AccountId>, &'static str> {
let _ = submit_candidates_with_self_vote::<T>(m, "fill_seats_up_to")?;
assert_eq!(Candidates::<T>::get().len() as u32, m, "wrong number of candidates.");
Pallet::<T>::do_phragmen();
assert_eq!(Candidates::<T>::get().len(), 0, "some candidates remaining.");
assert_eq!(
Members::<T>::get().len() + RunnersUp::<T>::get().len(),
m as usize,
"wrong number of members and runners-up",
);
Ok(Members::<T>::get()
.into_iter()
.map(|m| m.who)
.chain(RunnersUp::<T>::get().into_iter().map(|r| r.who))
.collect())
}
fn clean<T: Config>() {
Members::<T>::kill();
Candidates::<T>::kill();
RunnersUp::<T>::kill();
#[allow(deprecated)]
Voting::<T>::remove_all(None);
}
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn vote_equal(v: Linear<1, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
clean::<T>();
let all_candidates = submit_candidates::<T>(v, "candidates")?;
let caller = endowed_account::<T>("caller", 0);
let stake = default_stake::<T>(v);
let mut votes = all_candidates;
submit_voter::<T>(caller.clone(), votes.clone(), stake)?;
votes.rotate_left(1);
whitelist!(caller);
#[extrinsic_call]
vote(RawOrigin::Signed(caller), votes, stake);
Ok(())
}
#[benchmark]
fn vote_more(v: Linear<2, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
clean::<T>();
let all_candidates = submit_candidates::<T>(v, "candidates")?;
let caller = endowed_account::<T>("caller", 0);
let stake = default_stake::<T>(v) * BalanceOf::<T>::from(10_u32);
let mut votes = all_candidates.iter().skip(1).cloned().collect::<Vec<_>>();
submit_voter::<T>(caller.clone(), votes.clone(), stake / BalanceOf::<T>::from(10_u32))?;
votes = all_candidates;
assert!(votes.len() > Voting::<T>::get(caller.clone()).votes.len());
whitelist!(caller);
#[extrinsic_call]
vote(RawOrigin::Signed(caller), votes, stake / BalanceOf::<T>::from(10_u32));
Ok(())
}
#[benchmark]
fn vote_less(v: Linear<2, { T::MaxVotesPerVoter::get() }>) -> Result<(), BenchmarkError> {
clean::<T>();
let all_candidates = submit_candidates::<T>(v, "candidates")?;
let caller = endowed_account::<T>("caller", 0);
let stake = default_stake::<T>(v);
let mut votes = all_candidates;
submit_voter::<T>(caller.clone(), votes.clone(), stake)?;
votes = votes.into_iter().skip(1).collect::<Vec<_>>();
assert!(votes.len() < Voting::<T>::get(caller.clone()).votes.len());
whitelist!(caller);
#[extrinsic_call]
vote(RawOrigin::Signed(caller), votes, stake);
Ok(())
}
#[benchmark]
fn remove_voter() -> Result<(), BenchmarkError> {
let v = T::MaxVotesPerVoter::get();
clean::<T>();
let all_candidates = submit_candidates::<T>(v, "candidates")?;
let caller = endowed_account::<T>("caller", 0);
let stake = default_stake::<T>(v);
submit_voter::<T>(caller.clone(), all_candidates, stake)?;
whitelist!(caller);
#[extrinsic_call]
_(RawOrigin::Signed(caller));
Ok(())
}
#[benchmark]
fn submit_candidacy(
c: Linear<1, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
clean::<T>();
let _ = fill_seats_up_to::<T>(m)?;
let _ = submit_candidates::<T>(c, "candidates")?;
let candidate_account = endowed_account::<T>("caller", 0);
whitelist!(candidate_account);
#[extrinsic_call]
_(RawOrigin::Signed(candidate_account), candidate_count::<T>());
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
#[benchmark]
fn renounce_candidacy_candidate(
c: Linear<1, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
clean::<T>();
let _ = fill_seats_up_to::<T>(m)?;
let all_candidates = submit_candidates::<T>(c, "caller")?;
let bailing = all_candidates[0].clone(); let count = candidate_count::<T>();
whitelist!(bailing);
#[extrinsic_call]
renounce_candidacy(RawOrigin::Signed(bailing), Renouncing::Candidate(count));
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
#[benchmark]
fn renounce_candidacy_members() -> Result<(), BenchmarkError> {
let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
clean::<T>();
let members_and_runners_up = fill_seats_up_to::<T>(m)?;
let bailing = members_and_runners_up[0].clone();
assert!(Pallet::<T>::is_member(&bailing));
whitelist!(bailing);
#[extrinsic_call]
renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::Member);
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
#[benchmark]
fn renounce_candidacy_runners_up() -> Result<(), BenchmarkError> {
let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
clean::<T>();
let members_and_runners_up = fill_seats_up_to::<T>(m)?;
let bailing = members_and_runners_up[T::DesiredMembers::get() as usize + 1].clone();
assert!(Pallet::<T>::is_runner_up(&bailing));
whitelist!(bailing);
#[extrinsic_call]
renounce_candidacy(RawOrigin::Signed(bailing.clone()), Renouncing::RunnerUp);
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
#[benchmark]
fn remove_member_without_replacement() -> Result<(), BenchmarkError> {
#[block]
{
Err(BenchmarkError::Override(BenchmarkResult::from_weight(
T::BlockWeights::get().max_block,
)))?;
}
Ok(())
}
#[benchmark]
fn remove_member_with_replacement() -> Result<(), BenchmarkError> {
let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get();
clean::<T>();
let _ = fill_seats_up_to::<T>(m)?;
let removing = as_lookup::<T>(Pallet::<T>::members_ids()[0].clone());
#[extrinsic_call]
remove_member(RawOrigin::Root, removing, true, false);
assert_eq!(Members::<T>::get().len() as u32, T::DesiredMembers::get());
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
#[benchmark]
fn clean_defunct_voters(
v: Linear<{ T::MaxVoters::get() / 2 }, { T::MaxVoters::get() }>,
d: Linear<0, { T::MaxVoters::get() / 2 }>,
) -> Result<(), BenchmarkError> {
clean::<T>();
let all_candidates = submit_candidates::<T>(T::MaxCandidates::get(), "candidates")?;
distribute_voters::<T>(all_candidates, v, T::MaxVotesPerVoter::get() as usize)?;
Candidates::<T>::kill();
assert!(Voting::<T>::iter().all(|(_, v)| Pallet::<T>::is_defunct_voter(&v.votes)));
assert_eq!(Voting::<T>::iter().count() as u32, v);
#[extrinsic_call]
_(RawOrigin::Root, v, d);
assert_eq!(Voting::<T>::iter().count() as u32, v - d);
Ok(())
}
#[benchmark]
fn election_phragmen(
c: Linear<1, { T::MaxCandidates::get() }>,
v: Linear<1, { T::MaxVoters::get() }>,
e: Linear<{ T::MaxVoters::get() }, { T::MaxVoters::get() * T::MaxVotesPerVoter::get() }>,
) -> Result<(), BenchmarkError> {
clean::<T>();
let votes_per_voter = (e / v).min(T::MaxVotesPerVoter::get());
let all_candidates = submit_candidates_with_self_vote::<T>(c, "candidates")?;
let _ =
distribute_voters::<T>(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?;
#[block]
{
Pallet::<T>::on_initialize(T::TermDuration::get());
}
assert_eq!(Members::<T>::get().len() as u32, T::DesiredMembers::get().min(c));
assert_eq!(
RunnersUp::<T>::get().len() as u32,
T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())),
);
#[cfg(test)]
MEMBERS.with(|m| *m.borrow_mut() = vec![]);
Ok(())
}
impl_benchmark_test_suite! {
Pallet,
tests::ExtBuilder::default().desired_members(13).desired_runners_up(7),
tests::Test,
exec_name = build_and_execute,
}
}