feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,667 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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 alloc::{borrow::Cow, vec, vec::Vec};
use assert_matches::assert_matches;
use pezframe_benchmarking::v1::{
account, benchmarks_instance_pallet, whitelist_account, BenchmarkError,
};
use pezframe_support::{
assert_ok,
traits::{Currency, EnsureOrigin, EnsureOriginWithArg, UnfilteredDispatchable},
};
use pezframe_system::RawOrigin;
use pezsp_runtime::traits::Bounded as ArithBounded;
const SEED: u32 = 0;
fn set_block_number<T: Config<I>, I: 'static>(n: BlockNumberFor<T, I>) {
<T as Config<I>>::BlockNumberProvider::set_block_number(n);
}
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
}
fn funded_account<T: Config<I>, I: 'static>(name: &'static str, index: u32) -> T::AccountId {
let caller: T::AccountId = account(name, index, SEED);
T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
caller
}
fn dummy_call<T: Config<I>, I: 'static>() -> BoundedCallOf<T, I> {
let inner = pezframe_system::Call::remark { remark: vec![] };
let call = <T as Config<I>>::RuntimeCall::from(inner);
T::Preimages::bound(call).unwrap()
}
fn create_referendum<T: Config<I>, I: 'static>(origin: T::RuntimeOrigin) -> ReferendumIndex {
if let Ok(caller) = pezframe_system::ensure_signed(origin.clone()) {
T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
whitelist_account!(caller);
}
let proposal_origin = Box::new(RawOrigin::Root.into());
let proposal = dummy_call::<T, I>();
let enactment_moment = DispatchTime::After(0u32.into());
let call = crate::Call::<T, I>::submit { proposal_origin, proposal, enactment_moment };
assert_ok!(call.dispatch_bypass_filter(origin.clone()));
let index = ReferendumCount::<T, I>::get() - 1;
index
}
fn place_deposit<T: Config<I>, I: 'static>(index: ReferendumIndex) {
let caller = funded_account::<T, I>("caller", 0);
whitelist_account!(caller);
assert_ok!(Referenda::<T, I>::place_decision_deposit(RawOrigin::Signed(caller).into(), index));
}
fn nudge<T: Config<I>, I: 'static>(index: ReferendumIndex) {
assert_ok!(Referenda::<T, I>::nudge_referendum(RawOrigin::Root.into(), index));
}
fn fill_queue<T: Config<I>, I: 'static>(
origin: T::RuntimeOrigin,
index: ReferendumIndex,
spaces: u32,
pass_after: u32,
) -> Vec<ReferendumIndex> {
// First, create enough other referendums to fill the track.
let mut others = vec![];
for _ in 0..info::<T, I>(index).max_deciding {
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(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 index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
make_passing_after::<T, I>(index, Perbill::from_percent(pass_after));
others.push(index);
}
// Skip to when they can start being decided.
skip_prepare_period::<T, I>(index);
// Manually nudge the other referenda first to ensure that they begin.
others.iter().for_each(|&i| nudge::<T, I>(i));
others
}
fn info<T: Config<I>, I: 'static>(index: ReferendumIndex) -> Cow<'static, TrackInfoOf<T, I>> {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
T::Tracks::info(status.track).expect("Id value returned from T::Tracks")
}
fn make_passing_after<T: Config<I>, I: 'static>(index: ReferendumIndex, period_portion: Perbill) {
// We add an extra 1 percent to handle any perbill rounding errors which may cause
// a proposal to not actually pass.
let support = info::<T, I>(index)
.min_support
.threshold(period_portion)
.saturating_add(Perbill::from_percent(1));
let approval = info::<T, I>(index)
.min_approval
.threshold(period_portion)
.saturating_add(Perbill::from_percent(1));
Referenda::<T, I>::access_poll(index, |status| {
if let PollStatus::Ongoing(tally, class) = status {
T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32));
*tally = T::Tally::from_requirements(support, approval, class);
}
});
}
fn make_passing<T: Config<I>, I: 'static>(index: ReferendumIndex) {
Referenda::<T, I>::access_poll(index, |status| {
if let PollStatus::Ongoing(tally, class) = status {
T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32));
*tally = T::Tally::unanimity(class);
}
});
}
fn make_failing<T: Config<I>, I: 'static>(index: ReferendumIndex) {
Referenda::<T, I>::access_poll(index, |status| {
if let PollStatus::Ongoing(tally, class) = status {
T::Tally::setup(class, Perbill::from_rational(1u32, 1000u32));
*tally = T::Tally::rejection(class);
}
});
}
fn skip_prepare_period<T: Config<I>, I: 'static>(index: ReferendumIndex) {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
let prepare_period_over = status.submitted + info::<T, I>(index).prepare_period;
set_block_number::<T, I>(prepare_period_over);
}
fn skip_decision_period<T: Config<I>, I: 'static>(index: ReferendumIndex) {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
let decision_period_over = status.deciding.unwrap().since + info::<T, I>(index).decision_period;
set_block_number::<T, I>(decision_period_over);
}
fn skip_confirm_period<T: Config<I>, I: 'static>(index: ReferendumIndex) {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
let confirm_period_over = status.deciding.unwrap().confirming.unwrap();
set_block_number::<T, I>(confirm_period_over);
}
fn skip_timeout_period<T: Config<I>, I: 'static>(index: ReferendumIndex) {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
let timeout_period_over = status.submitted + T::UndecidingTimeout::get();
set_block_number::<T, I>(timeout_period_over);
}
fn alarm_time<T: Config<I>, I: 'static>(index: ReferendumIndex) -> BlockNumberFor<T, I> {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
status.alarm.unwrap().0
}
fn is_confirming<T: Config<I>, I: 'static>(index: ReferendumIndex) -> bool {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
matches!(
status,
ReferendumStatus { deciding: Some(DecidingStatus { confirming: Some(_), .. }), .. }
)
}
fn is_not_confirming<T: Config<I>, I: 'static>(index: ReferendumIndex) -> bool {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
matches!(
status,
ReferendumStatus { deciding: Some(DecidingStatus { confirming: None, .. }), .. }
)
}
benchmarks_instance_pallet! {
submit {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
if let Ok(caller) = pezframe_system::ensure_signed(origin.clone()) {
T::Currency::make_free_balance_be(&caller, BalanceOf::<T, I>::max_value());
whitelist_account!(caller);
}
}: _<T::RuntimeOrigin>(
origin,
Box::new(RawOrigin::Root.into()),
dummy_call::<T, I>(),
DispatchTime::After(0u32.into())
) verify {
let index = ReferendumCount::<T, I>::get().checked_sub(1).unwrap();
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Ongoing(_)));
}
place_decision_deposit_preparing {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
}: place_decision_deposit<T::RuntimeOrigin>(origin, index)
verify {
assert!(Referenda::<T, I>::ensure_ongoing(index).unwrap().decision_deposit.is_some());
}
place_decision_deposit_queued {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
fill_queue::<T, I>(origin.clone(), index, 1, 90);
}: place_decision_deposit<T::RuntimeOrigin>(origin, index)
verify {
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert!(TrackQueue::<T, I>::get(&track).contains(&(index, 0u32.into())));
}
place_decision_deposit_not_queued {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
fill_queue::<T, I>(origin.clone(), index, 0, 90);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert!(TrackQueue::<T, I>::get(&track).into_iter().all(|(i, _)| i != index));
}: place_decision_deposit<T::RuntimeOrigin>(origin, index)
verify {
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert!(TrackQueue::<T, I>::get(&track).into_iter().all(|(i, _)| i != index));
}
place_decision_deposit_passing {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
skip_prepare_period::<T, I>(index);
make_passing::<T, I>(index);
}: place_decision_deposit<T::RuntimeOrigin>(origin, index)
verify {
assert!(is_confirming::<T, I>(index));
}
place_decision_deposit_failing {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
skip_prepare_period::<T, I>(index);
}: place_decision_deposit<T::RuntimeOrigin>(origin, index)
verify {
assert!(is_not_confirming::<T, I>(index));
}
refund_decision_deposit {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
assert_ok!(Referenda::<T, I>::cancel(
T::CancelOrigin::try_successful_origin()
.expect("CancelOrigin has no successful origin required for the benchmark"),
index,
));
}: _<T::RuntimeOrigin>(origin, index)
verify {
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Cancelled(_, _, None)));
}
refund_submission_deposit {
let origin =
T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?;
let index = create_referendum::<T, I>(origin.clone());
let caller = pezframe_system::ensure_signed(origin.clone()).unwrap();
let balance = T::Currency::free_balance(&caller);
assert_ok!(Referenda::<T, I>::cancel(
T::CancelOrigin::try_successful_origin()
.expect("CancelOrigin has no successful origin required for the benchmark"),
index,
));
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Cancelled(_, Some(_), _)));
}: _<T::RuntimeOrigin>(origin, index)
verify {
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Cancelled(_, None, _)));
let new_balance = T::Currency::free_balance(&caller);
// the deposit is zero or make sure it was unreserved.
assert!(T::SubmissionDeposit::get().is_zero() || new_balance > balance);
}
cancel {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
}: _<T::RuntimeOrigin>(
T::CancelOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?,
index
) verify {
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Cancelled(..)));
}
kill {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
}: _<T::RuntimeOrigin>(
T::KillOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?,
index
) verify {
assert_matches!(ReferendumInfoFor::<T, I>::get(index), Some(ReferendumInfo::Killed(..)));
}
one_fewer_deciding_queue_empty {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
nudge::<T, I>(index);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_ok!(Referenda::<T, I>::cancel(
T::CancelOrigin::try_successful_origin()
.expect("CancelOrigin has no successful origin required for the benchmark"),
index,
));
assert_eq!(DecidingCount::<T, I>::get(&track), 1);
}: one_fewer_deciding(RawOrigin::Root, track)
verify {
assert_eq!(DecidingCount::<T, I>::get(&track), 0);
}
one_fewer_deciding_failing {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin.clone());
// No spaces free in the queue.
let queued = fill_queue::<T, I>(origin, index, 0, 90);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_ok!(Referenda::<T, I>::cancel(
T::CancelOrigin::try_successful_origin()
.expect("CancelOrigin has no successful origin required for the benchmark"),
queued[0],
));
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
let deciding_count = DecidingCount::<T, I>::get(&track);
}: one_fewer_deciding(RawOrigin::Root, track)
verify {
assert_eq!(DecidingCount::<T, I>::get(&track), deciding_count);
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get() - 1);
assert!(queued.into_iter().skip(1).all(|i| Referenda::<T, I>::ensure_ongoing(i)
.unwrap()
.deciding
.map_or(true, |d| d.confirming.is_none())
));
}
one_fewer_deciding_passing {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin.clone());
// No spaces free in the queue.
let queued = fill_queue::<T, I>(origin, index, 0, 0);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_ok!(Referenda::<T, I>::cancel(
T::CancelOrigin::try_successful_origin()
.expect("CancelOrigin has no successful origin required for the benchmark"),
queued[0],
));
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
let deciding_count = DecidingCount::<T, I>::get(&track);
}: one_fewer_deciding(RawOrigin::Root, track)
verify {
assert_eq!(DecidingCount::<T, I>::get(&track), deciding_count);
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get() - 1);
assert!(queued.into_iter().skip(1).filter(|i| Referenda::<T, I>::ensure_ongoing(*i)
.unwrap()
.deciding
.map_or(false, |d| d.confirming.is_some())
).count() == 1);
}
nudge_referendum_requeued_insertion {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
// First create our referendum and place the deposit. It will be failing.
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
fill_queue::<T, I>(origin, 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::<T, I>(index);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert!(TrackQueue::<T, I>::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::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let t = TrackQueue::<T, I>::get(&track);
assert_eq!(t.len() as u32, T::MaxQueued::get());
assert_eq!(t[t.len() - 1].0, index);
}
nudge_referendum_requeued_slide {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
// First create our referendum and place the deposit. It will be failing.
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
fill_queue::<T, I>(origin, 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::<T, I>(index);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert_eq!(TrackQueue::<T, I>::get(&track)[0], (index, 0u32.into()));
// Now alter the voting, so that ours leap-frogs all into the best position.
make_passing::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let t = TrackQueue::<T, I>::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.
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
// First create our referendum and place the deposit. It will be failing.
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
fill_queue::<T, I>(origin, index, 1, 0);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get() - 1);
assert!(TrackQueue::<T, I>::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::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert_eq!(TrackQueue::<T, I>::get(&track)[0], (index, 0u32.into()));
}
nudge_referendum_not_queued {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
// First create our referendum and place the deposit. It will be failing.
let index = create_referendum::<T, I>(origin.clone());
place_deposit::<T, I>(index);
fill_queue::<T, I>(origin, index, 0, 0);
let track = Referenda::<T, I>::ensure_ongoing(index).unwrap().track;
assert_eq!(TrackQueue::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert!(TrackQueue::<T, I>::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::<T, I>::get(&track).len() as u32, T::MaxQueued::get());
assert!(TrackQueue::<T, I>::get(&track).into_iter().all(|(i, _)| i != index));
}
nudge_referendum_no_deposit {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
skip_prepare_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
assert_matches!(status, ReferendumStatus { deciding: None, .. });
}
nudge_referendum_preparing {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let status = Referenda::<T, I>::ensure_ongoing(index).unwrap();
assert_matches!(status, ReferendumStatus { deciding: None, .. });
}
nudge_referendum_timed_out {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
skip_timeout_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let info = ReferendumInfoFor::<T, I>::get(index).unwrap();
assert_matches!(info, ReferendumInfo::TimedOut(..));
}
nudge_referendum_begin_deciding_failing {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert!(is_not_confirming::<T, I>(index));
}
nudge_referendum_begin_deciding_passing {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
make_passing::<T, I>(index);
skip_prepare_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert!(is_confirming::<T, I>(index));
}
nudge_referendum_begin_confirming {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
nudge::<T, I>(index);
assert!(!is_confirming::<T, I>(index));
make_passing::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert!(is_confirming::<T, I>(index));
}
nudge_referendum_end_confirming {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
make_passing::<T, I>(index);
nudge::<T, I>(index);
assert!(is_confirming::<T, I>(index));
make_failing::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert!(!is_confirming::<T, I>(index));
}
nudge_referendum_continue_not_confirming {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
nudge::<T, I>(index);
assert!(!is_confirming::<T, I>(index));
let old_alarm = alarm_time::<T, I>(index);
make_passing_after::<T, I>(index, Perbill::from_percent(50));
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert_ne!(old_alarm, alarm_time::<T, I>(index));
assert!(!is_confirming::<T, I>(index));
}
nudge_referendum_continue_confirming {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
make_passing::<T, I>(index);
skip_prepare_period::<T, I>(index);
nudge::<T, I>(index);
assert!(is_confirming::<T, I>(index));
let old_alarm = alarm_time::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
assert!(is_confirming::<T, I>(index));
}
nudge_referendum_approved {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
make_passing::<T, I>(index);
nudge::<T, I>(index);
skip_confirm_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let info = ReferendumInfoFor::<T, I>::get(index).unwrap();
assert_matches!(info, ReferendumInfo::Approved(..));
}
nudge_referendum_rejected {
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin);
place_deposit::<T, I>(index);
skip_prepare_period::<T, I>(index);
make_failing::<T, I>(index);
nudge::<T, I>(index);
skip_decision_period::<T, I>(index);
}: nudge_referendum(RawOrigin::Root, index)
verify {
let info = ReferendumInfoFor::<T, I>::get(index).unwrap();
assert_matches!(info, ReferendumInfo::Rejected(..));
}
set_some_metadata {
use alloc::borrow::Cow;
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin.clone());
let hash = T::Preimages::note(Cow::from(vec![5, 6])).unwrap();
}: set_metadata<T::RuntimeOrigin>(origin, index, Some(hash))
verify {
assert_last_event::<T, I>(Event::MetadataSet { index, hash }.into());
}
clear_metadata {
use alloc::borrow::Cow;
let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into())
.expect("SubmitOrigin has no successful origin required for the benchmark");
let index = create_referendum::<T, I>(origin.clone());
let hash = T::Preimages::note(Cow::from(vec![6, 7, 8])).unwrap();
assert_ok!(
Referenda::<T, I>::set_metadata(origin.clone(), index, Some(hash))
);
}: set_metadata<T::RuntimeOrigin>(origin, index, None)
verify {
assert_last_event::<T, I>(Event::MetadataCleared { index, hash }.into());
}
impl_benchmark_test_suite!(
Referenda,
crate::mock::ExtBuilder::default().build(),
crate::mock::Test
);
}
+180
View File
@@ -0,0 +1,180 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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.
//! Helpers for managing the different weights in various algorithmic branches.
use super::Config;
use crate::weights::WeightInfo;
use pezframe_support::weights::Weight;
/// Branches within the `begin_deciding` function.
pub enum BeginDecidingBranch {
Passing,
Failing,
}
/// Branches within the `service_referendum` function.
pub enum ServiceBranch {
Fail,
NoDeposit,
Preparing,
Queued,
NotQueued,
RequeuedInsertion,
RequeuedSlide,
BeginDecidingPassing,
BeginDecidingFailing,
BeginConfirming,
ContinueConfirming,
EndConfirming,
ContinueNotConfirming,
Approved,
Rejected,
TimedOut,
}
impl From<BeginDecidingBranch> for ServiceBranch {
fn from(x: BeginDecidingBranch) -> Self {
use BeginDecidingBranch::*;
use ServiceBranch::*;
match x {
Passing => BeginDecidingPassing,
Failing => BeginDecidingFailing,
}
}
}
impl ServiceBranch {
/// Return the weight of the `nudge` function when it takes the branch denoted by `self`.
pub fn weight_of_nudge<T: Config<I>, I: 'static>(self) -> pezframe_support::weights::Weight {
use ServiceBranch::*;
match self {
NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(),
Preparing => T::WeightInfo::nudge_referendum_preparing(),
Queued => T::WeightInfo::nudge_referendum_queued(),
NotQueued => T::WeightInfo::nudge_referendum_not_queued(),
RequeuedInsertion => T::WeightInfo::nudge_referendum_requeued_insertion(),
RequeuedSlide => T::WeightInfo::nudge_referendum_requeued_slide(),
BeginDecidingPassing => T::WeightInfo::nudge_referendum_begin_deciding_passing(),
BeginDecidingFailing => T::WeightInfo::nudge_referendum_begin_deciding_failing(),
BeginConfirming => T::WeightInfo::nudge_referendum_begin_confirming(),
ContinueConfirming => T::WeightInfo::nudge_referendum_continue_confirming(),
EndConfirming => T::WeightInfo::nudge_referendum_end_confirming(),
ContinueNotConfirming => T::WeightInfo::nudge_referendum_continue_not_confirming(),
Approved => T::WeightInfo::nudge_referendum_approved(),
Rejected => T::WeightInfo::nudge_referendum_rejected(),
TimedOut | Fail => T::WeightInfo::nudge_referendum_timed_out(),
}
}
/// Return the maximum possible weight of the `nudge` function.
pub fn max_weight_of_nudge<T: Config<I>, I: 'static>() -> pezframe_support::weights::Weight {
Weight::zero()
.max(T::WeightInfo::nudge_referendum_no_deposit())
.max(T::WeightInfo::nudge_referendum_preparing())
.max(T::WeightInfo::nudge_referendum_queued())
.max(T::WeightInfo::nudge_referendum_not_queued())
.max(T::WeightInfo::nudge_referendum_requeued_insertion())
.max(T::WeightInfo::nudge_referendum_requeued_slide())
.max(T::WeightInfo::nudge_referendum_begin_deciding_passing())
.max(T::WeightInfo::nudge_referendum_begin_deciding_failing())
.max(T::WeightInfo::nudge_referendum_begin_confirming())
.max(T::WeightInfo::nudge_referendum_continue_confirming())
.max(T::WeightInfo::nudge_referendum_end_confirming())
.max(T::WeightInfo::nudge_referendum_continue_not_confirming())
.max(T::WeightInfo::nudge_referendum_approved())
.max(T::WeightInfo::nudge_referendum_rejected())
.max(T::WeightInfo::nudge_referendum_timed_out())
}
/// Return the weight of the `place_decision_deposit` function when it takes the branch denoted
/// by `self`.
pub fn weight_of_deposit<T: Config<I>, I: 'static>(
self,
) -> Option<pezframe_support::weights::Weight> {
use ServiceBranch::*;
let ref_time_weight = match self {
Preparing => T::WeightInfo::place_decision_deposit_preparing(),
Queued => T::WeightInfo::place_decision_deposit_queued(),
NotQueued => T::WeightInfo::place_decision_deposit_not_queued(),
BeginDecidingPassing => T::WeightInfo::place_decision_deposit_passing(),
BeginDecidingFailing => T::WeightInfo::place_decision_deposit_failing(),
BeginConfirming |
ContinueConfirming |
EndConfirming |
ContinueNotConfirming |
Approved |
Rejected |
RequeuedInsertion |
RequeuedSlide |
TimedOut |
Fail |
NoDeposit => return None,
};
Some(ref_time_weight)
}
/// Return the maximum possible weight of the `place_decision_deposit` function.
pub fn max_weight_of_deposit<T: Config<I>, I: 'static>() -> pezframe_support::weights::Weight {
Weight::zero()
.max(T::WeightInfo::place_decision_deposit_preparing())
.max(T::WeightInfo::place_decision_deposit_queued())
.max(T::WeightInfo::place_decision_deposit_not_queued())
.max(T::WeightInfo::place_decision_deposit_passing())
.max(T::WeightInfo::place_decision_deposit_failing())
}
}
/// Branches that the `one_fewer_deciding` function may take.
pub enum OneFewerDecidingBranch {
QueueEmpty,
BeginDecidingPassing,
BeginDecidingFailing,
}
impl From<BeginDecidingBranch> for OneFewerDecidingBranch {
fn from(x: BeginDecidingBranch) -> Self {
use BeginDecidingBranch::*;
use OneFewerDecidingBranch::*;
match x {
Passing => BeginDecidingPassing,
Failing => BeginDecidingFailing,
}
}
}
impl OneFewerDecidingBranch {
/// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted
/// by `self`.
pub fn weight<T: Config<I>, I: 'static>(self) -> pezframe_support::weights::Weight {
use OneFewerDecidingBranch::*;
match self {
QueueEmpty => T::WeightInfo::one_fewer_deciding_queue_empty(),
BeginDecidingPassing => T::WeightInfo::one_fewer_deciding_passing(),
BeginDecidingFailing => T::WeightInfo::one_fewer_deciding_failing(),
}
}
/// Return the maximum possible weight of the `one_fewer_deciding` function.
pub fn max_weight<T: Config<I>, I: 'static>() -> pezframe_support::weights::Weight {
Weight::zero()
.max(T::WeightInfo::one_fewer_deciding_queue_empty())
.max(T::WeightInfo::one_fewer_deciding_passing())
.max(T::WeightInfo::one_fewer_deciding_failing())
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,417 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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.
//! Storage migrations for the referenda pallet.
use super::*;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use pezframe_support::{pezpallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade};
use log;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
type SystemBlockNumberFor<T> = pezframe_system::pezpallet_prelude::BlockNumberFor<T>;
/// Initial version of storage types.
pub mod v0 {
use super::*;
// ReferendumStatus and its dependency types referenced from the latest version while staying
// unchanged. [`super::test::referendum_status_v0()`] checks its immutability between v0 and
// latest version.
#[cfg(test)]
pub(super) use super::{ReferendumStatus, ReferendumStatusOf};
pub type ReferendumInfoOf<T, I> = ReferendumInfo<
TrackIdOf<T, I>,
PalletsOriginOf<T>,
SystemBlockNumberFor<T>,
BoundedCallOf<T, I>,
BalanceOf<T, I>,
TallyOf<T, I>,
<T as pezframe_system::Config>::AccountId,
ScheduleAddressOf<T, I>,
>;
/// Info regarding a referendum, present or past.
#[derive(
Encode,
Decode,
Clone,
PartialEq,
Eq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
DecodeWithMemTracking,
)]
pub enum ReferendumInfo<
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
> {
/// Referendum has been submitted and is being voted on.
Ongoing(
ReferendumStatus<
TrackId,
RuntimeOrigin,
Moment,
Call,
Balance,
Tally,
AccountId,
ScheduleAddress,
>,
),
/// Referendum finished with approval. Submission deposit is held.
Approved(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with rejection. Submission deposit is held.
Rejected(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with cancellation. Submission deposit is held.
Cancelled(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished and was never decided. Submission deposit is held.
TimedOut(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with a kill.
Killed(Moment),
}
#[storage_alias]
pub type ReferendumInfoFor<T: Config<I>, I: 'static> =
StorageMap<Pallet<T, I>, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
}
pub mod v1 {
use super::*;
/// The log target.
const TARGET: &'static str = "runtime::referenda::migration::v1";
pub(crate) type ReferendumInfoOf<T, I> = ReferendumInfo<
TrackIdOf<T, I>,
PalletsOriginOf<T>,
SystemBlockNumberFor<T>,
BoundedCallOf<T, I>,
BalanceOf<T, I>,
TallyOf<T, I>,
<T as pezframe_system::Config>::AccountId,
ScheduleAddressOf<T, I>,
>;
#[storage_alias]
pub type ReferendumInfoFor<T: Config<I>, I: 'static> =
StorageMap<Pallet<T, I>, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
/// Transforms a submission deposit of ReferendumInfo(Approved|Rejected|Cancelled|TimedOut) to
/// optional value, making it refundable.
pub struct MigrateV0ToV1<T, I = ()>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1<T, I> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
let referendum_count = v0::ReferendumInfoFor::<T, I>::iter().count();
log::info!(
target: TARGET,
"pre-upgrade state contains '{}' referendums.",
referendum_count
);
Ok((referendum_count as u32).encode())
}
fn on_runtime_upgrade() -> Weight {
let in_code_version = Pallet::<T, I>::in_code_storage_version();
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
let mut weight = T::DbWeight::get().reads(1);
log::info!(
target: TARGET,
"running migration with in-code storage version {:?} / onchain {:?}.",
in_code_version,
on_chain_version
);
if on_chain_version != 0 {
log::warn!(target: TARGET, "skipping migration from v0 to v1.");
return weight;
}
v0::ReferendumInfoFor::<T, I>::iter().for_each(|(key, value)| {
let maybe_new_value = match value {
v0::ReferendumInfo::Ongoing(_) | v0::ReferendumInfo::Killed(_) => None,
v0::ReferendumInfo::Approved(e, s, d) =>
Some(ReferendumInfo::Approved(e, Some(s), d)),
v0::ReferendumInfo::Rejected(e, s, d) =>
Some(ReferendumInfo::Rejected(e, Some(s), d)),
v0::ReferendumInfo::Cancelled(e, s, d) =>
Some(ReferendumInfo::Cancelled(e, Some(s), d)),
v0::ReferendumInfo::TimedOut(e, s, d) =>
Some(ReferendumInfo::TimedOut(e, Some(s), d)),
};
if let Some(new_value) = maybe_new_value {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
log::info!(target: TARGET, "migrating referendum #{:?}", &key);
v1::ReferendumInfoFor::<T, I>::insert(key, new_value);
} else {
weight.saturating_accrue(T::DbWeight::get().reads(1));
}
});
StorageVersion::new(1).put::<Pallet<T, I>>();
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
ensure!(on_chain_version == 1, "must upgrade from version 0 to 1.");
let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
.expect("failed to decode the state from pre-upgrade.");
let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
log::info!(target: TARGET, "migrated all referendums.");
Ok(())
}
}
}
/// Migration for when changing the block number provider.
///
/// This migration is not guarded
pub mod switch_block_number_provider {
use super::*;
/// The log target.
const TARGET: &'static str = "runtime::referenda::migration::change_block_number_provider";
/// Convert from one to another block number provider/type.
pub trait BlockNumberConversion<Old, New> {
/// Convert the `old` block number type to the new block number type.
///
/// Any changes in the rate of blocks need to be taken into account.
fn convert_block_number(block_number: Old) -> New;
}
/// Transforms `SystemBlockNumberFor<T>` to `BlockNumberFor<T,I>`
pub struct MigrateBlockNumberProvider<BlockConverter, T, I = ()>(
PhantomData<(T, I)>,
PhantomData<BlockConverter>,
);
impl<BlockConverter: BlockNumberConversion<T, I>, T: Config<I>, I: 'static> OnRuntimeUpgrade
for MigrateBlockNumberProvider<BlockConverter, T, I>
where
BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
T: Config<I>,
{
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
let referendum_count = v1::ReferendumInfoFor::<T, I>::iter().count();
log::info!(
target: TARGET,
"pre-upgrade state contains '{}' referendums.",
referendum_count
);
Ok((referendum_count as u32).encode())
}
fn on_runtime_upgrade() -> Weight {
let mut weight = Weight::zero();
weight.saturating_accrue(migrate_block_number_provider::<BlockConverter, T, I>());
weight
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
ensure!(on_chain_version == 1, "must upgrade from version 1 to 2.");
let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
.expect("failed to decode the state from pre-upgrade.");
let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
log::info!(target: TARGET, "migrated all referendums.");
Ok(())
}
}
pub fn migrate_block_number_provider<BlockConverter, T, I: 'static>() -> Weight
where
BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
T: Config<I>,
{
let in_code_version = Pallet::<T, I>::in_code_storage_version();
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
let mut weight = T::DbWeight::get().reads(1);
log::info!(
target: "runtime::referenda::migration::change_block_number_provider",
"running migration with in-code storage version {:?} / onchain {:?}.",
in_code_version,
on_chain_version
);
if on_chain_version == 0 {
log::error!(target: TARGET, "skipping migration from v0 to switch_block_number_provider.");
return weight;
}
// Migration logic here
v1::ReferendumInfoFor::<T, I>::iter().for_each(|(key, value)| {
let maybe_new_value = match value {
ReferendumInfo::Ongoing(_) | ReferendumInfo::Killed(_) => None,
ReferendumInfo::Approved(e, s, d) => {
let new_e = BlockConverter::convert_block_number(e);
Some(ReferendumInfo::Approved(new_e, s, d))
},
ReferendumInfo::Rejected(e, s, d) => {
let new_e = BlockConverter::convert_block_number(e);
Some(ReferendumInfo::Rejected(new_e, s, d))
},
ReferendumInfo::Cancelled(e, s, d) => {
let new_e = BlockConverter::convert_block_number(e);
Some(ReferendumInfo::Cancelled(new_e, s, d))
},
ReferendumInfo::TimedOut(e, s, d) => {
let new_e = BlockConverter::convert_block_number(e);
Some(ReferendumInfo::TimedOut(new_e, s, d))
},
};
if let Some(new_value) = maybe_new_value {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
log::info!(target: TARGET, "migrating referendum #{:?}", &key);
ReferendumInfoFor::<T, I>::insert(key, new_value);
} else {
weight.saturating_accrue(T::DbWeight::get().reads(1));
}
});
weight
}
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::{
migration::switch_block_number_provider::{
migrate_block_number_provider, BlockNumberConversion,
},
mock::{Test as T, *},
};
use core::str::FromStr;
// create referendum status v0.
fn create_status_v0() -> v0::ReferendumStatusOf<T, ()> {
let origin: OriginCaller = pezframe_system::RawOrigin::Root.into();
let track = <T as Config<()>>::Tracks::track_for(&origin).unwrap();
v0::ReferendumStatusOf::<T, ()> {
track,
in_queue: true,
origin,
proposal: set_balance_proposal_bounded(1),
enactment: DispatchTime::At(1),
tally: TallyOf::<T, ()>::new(track),
submission_deposit: Deposit { who: 1, amount: 10 },
submitted: 1,
decision_deposit: None,
alarm: None,
deciding: None,
}
}
#[test]
pub fn referendum_status_v0() {
// make sure the bytes of the encoded referendum v0 is decodable.
let ongoing_encoded = pezsp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
let ongoing_dec = v0::ReferendumInfoOf::<T, ()>::decode(&mut &*ongoing_encoded).unwrap();
let ongoing = v0::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
assert_eq!(ongoing, ongoing_dec);
}
#[test]
fn migration_v0_to_v1_works() {
ExtBuilder::default().build_and_execute(|| {
// create and insert into the storage an ongoing referendum v0.
let status_v0 = create_status_v0();
let ongoing_v0 = v0::ReferendumInfoOf::<T, ()>::Ongoing(status_v0.clone());
ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
v0::ReferendumInfoFor::<T, ()>::insert(2, ongoing_v0);
// create and insert into the storage an approved referendum v0.
let approved_v0 = v0::ReferendumInfoOf::<T, ()>::Approved(
123,
Deposit { who: 1, amount: 10 },
Some(Deposit { who: 2, amount: 20 }),
);
ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
v0::ReferendumInfoFor::<T, ()>::insert(5, approved_v0);
// run migration from v0 to v1.
v1::MigrateV0ToV1::<T, ()>::on_runtime_upgrade();
// fetch and assert migrated into v1 the ongoing referendum.
let ongoing_v1 = v1::ReferendumInfoFor::<T, ()>::get(2).unwrap();
// referendum status schema is the same for v0 and v1.
assert_eq!(ReferendumInfoOf::<T, ()>::Ongoing(status_v0), ongoing_v1);
// fetch and assert migrated into v1 the approved referendum.
let approved_v1 = v1::ReferendumInfoFor::<T, ()>::get(5).unwrap();
assert_eq!(
approved_v1,
ReferendumInfoOf::<T, ()>::Approved(
123,
Some(Deposit { who: 1, amount: 10 }),
Some(Deposit { who: 2, amount: 20 })
)
);
});
}
#[test]
fn migration_v1_to_switch_block_number_provider_works() {
ExtBuilder::default().build_and_execute(|| {
pub struct MockBlockConverter;
impl BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, ()>> for MockBlockConverter {
fn convert_block_number(block_number: SystemBlockNumberFor<T>) -> BlockNumberFor<T, ()> {
block_number as u64 + 10u64
}
}
let referendum_ongoing = v1::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
let referendum_approved = v1::ReferendumInfoOf::<T, ()>::Approved(
50, //old block number
Some(Deposit { who: 1, amount: 10 }),
Some(Deposit { who: 2, amount: 20 }),
);
ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
v1::ReferendumInfoFor::<T, ()>::insert(1, referendum_ongoing);
ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
v1::ReferendumInfoFor::<T, ()>::insert(2, referendum_approved);
migrate_block_number_provider::<MockBlockConverter, T, ()>();
let ongoing_v2 = ReferendumInfoFor::<T, ()>::get(1).unwrap();
assert_eq!(
ongoing_v2,
ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0())
);
let approved_v2 = ReferendumInfoFor::<T, ()>::get(2).unwrap();
assert_eq!(
approved_v2,
ReferendumInfoOf::<T, ()>::Approved(
50,
Some(Deposit { who: 1, amount: 10 }),
Some(Deposit { who: 2, amount: 20 })
)
);
});
}
}
+474
View File
@@ -0,0 +1,474 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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.
//! The crate's tests.
use super::*;
use crate::{self as pezpallet_referenda, types::Track};
use alloc::borrow::Cow;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezframe_support::{
assert_ok, derive_impl, ord_parameter_types, parameter_types,
traits::{
ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling,
},
weights::Weight,
};
use pezframe_system::{EnsureRoot, EnsureSignedBy};
use pezsp_runtime::{
str_array as s,
traits::{BlakeTwo256, Hash},
BuildStorage, DispatchResult, Perbill,
};
type Block = pezframe_system::mocking::MockBlock<Test>;
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
Preimage: pezpallet_preimage,
Scheduler: pezpallet_scheduler,
Referenda: pezpallet_referenda,
}
);
// Test that a fitlered call can be dispatched.
pub struct BaseFilter;
impl Contains<RuntimeCall> for BaseFilter {
fn contains(call: &RuntimeCall) -> bool {
!matches!(call, &RuntimeCall::Balances(pezpallet_balances::Call::force_set_balance { .. }))
}
}
parameter_types! {
pub MaxWeight: Weight = Weight::from_parts(2_000_000_000_000, u64::MAX);
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type BaseCallFilter = BaseFilter;
type Block = Block;
type AccountData = pezpallet_balances::AccountData<u64>;
}
impl pezpallet_preimage::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type Currency = Balances;
type ManagerOrigin = EnsureRoot<u64>;
type Consideration = ();
}
impl pezpallet_scheduler::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type PalletsOrigin = OriginCaller;
type RuntimeCall = RuntimeCall;
type MaximumWeight = MaxWeight;
type ScheduleOrigin = EnsureRoot<u64>;
type MaxScheduledPerBlock = ConstU32<100>;
type WeightInfo = ();
type OriginPrivilegeCmp = EqualPrivilegeOnly;
type Preimages = Preimage;
type BlockNumberProvider = pezframe_system::Pallet<Test>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type AccountStore = System;
}
parameter_types! {
pub static AlarmInterval: u64 = 1;
}
ord_parameter_types! {
pub const One: u64 = 1;
pub const Two: u64 = 2;
pub const Three: u64 = 3;
pub const Four: u64 = 4;
pub const Five: u64 = 5;
pub const Six: u64 = 6;
}
pub struct TestTracksInfo;
impl TracksInfo<u64, u64> for TestTracksInfo {
type Id = u8;
type RuntimeOrigin = <RuntimeOrigin as OriginTrait>::PalletsOrigin;
fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, u64, u64>>> {
static DATA: [Track<u8, u64, u64>; 3] = [
Track {
id: 0u8,
info: TrackInfo {
name: s("root"),
max_deciding: 1,
decision_deposit: 10,
prepare_period: 4,
decision_period: 4,
confirm_period: 2,
min_enactment_period: 4,
min_approval: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(50),
ceil: Perbill::from_percent(100),
},
min_support: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(0),
ceil: Perbill::from_percent(100),
},
},
},
Track {
id: 1u8,
info: TrackInfo {
name: s("none"),
max_deciding: 3,
decision_deposit: 1,
prepare_period: 2,
decision_period: 2,
confirm_period: 1,
min_enactment_period: 2,
min_approval: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(95),
ceil: Perbill::from_percent(100),
},
min_support: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(90),
ceil: Perbill::from_percent(100),
},
},
},
Track {
id: 2u8,
info: TrackInfo {
name: s("none"),
max_deciding: 3,
decision_deposit: 1,
prepare_period: 2,
decision_period: 2,
confirm_period: 1,
min_enactment_period: 0,
min_approval: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(95),
ceil: Perbill::from_percent(100),
},
min_support: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(90),
ceil: Perbill::from_percent(100),
},
},
},
];
DATA.iter().map(Cow::Borrowed)
}
fn track_for(id: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
if let Ok(system_origin) = pezframe_system::RawOrigin::try_from(id.clone()) {
match system_origin {
pezframe_system::RawOrigin::Root => Ok(0),
pezframe_system::RawOrigin::None => Ok(1),
pezframe_system::RawOrigin::Signed(1) => Ok(2),
_ => Err(()),
}
} else {
Err(())
}
}
}
impl Config for Test {
type WeightInfo = ();
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type Scheduler = Scheduler;
type Currency = pezpallet_balances::Pallet<Self>;
type SubmitOrigin = pezframe_system::EnsureSigned<u64>;
type CancelOrigin = EnsureSignedBy<Four, u64>;
type KillOrigin = EnsureRoot<u64>;
type Slash = ();
type Votes = u32;
type Tally = Tally;
type SubmissionDeposit = ConstU64<2>;
type MaxQueued = ConstU32<3>;
type UndecidingTimeout = ConstU64<20>;
type AlarmInterval = AlarmInterval;
type Tracks = TestTracksInfo;
type Preimages = Preimage;
type BlockNumberProvider = System;
}
pub struct ExtBuilder {}
impl Default for ExtBuilder {
fn default() -> Self {
Self {}
}
}
impl ExtBuilder {
pub fn build(self) -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)];
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = pezsp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
self.build().execute_with(|| {
test();
Referenda::do_try_state().unwrap();
})
}
}
#[derive(
Encode, Debug, Decode, DecodeWithMemTracking, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen,
)]
pub struct Tally {
pub ayes: u32,
pub nays: u32,
}
impl<Class> VoteTally<u32, Class> for Tally {
fn new(_: Class) -> Self {
Self { ayes: 0, nays: 0 }
}
fn ayes(&self, _: Class) -> u32 {
self.ayes
}
fn support(&self, _: Class) -> Perbill {
Perbill::from_percent(self.ayes)
}
fn approval(&self, _: Class) -> Perbill {
if self.ayes + self.nays > 0 {
Perbill::from_rational(self.ayes, self.ayes + self.nays)
} else {
Perbill::zero()
}
}
#[cfg(feature = "runtime-benchmarks")]
fn unanimity(_: Class) -> Self {
Self { ayes: 100, nays: 0 }
}
#[cfg(feature = "runtime-benchmarks")]
fn rejection(_: Class) -> Self {
Self { ayes: 0, nays: 100 }
}
#[cfg(feature = "runtime-benchmarks")]
fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self {
let ayes = support.mul_ceil(100u32);
let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes;
Self { ayes, nays }
}
#[cfg(feature = "runtime-benchmarks")]
fn setup(_: Class, _: Perbill) {}
}
pub fn set_balance_proposal(value: u64) -> Vec<u8> {
RuntimeCall::Balances(pezpallet_balances::Call::force_set_balance { who: 42, new_free: value })
.encode()
}
pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf<Test, ()> {
let c = RuntimeCall::Balances(pezpallet_balances::Call::force_set_balance {
who: 42,
new_free: value,
});
<Preimage as StorePreimage>::bound(c).unwrap()
}
#[allow(dead_code)]
pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult {
Referenda::submit(
RuntimeOrigin::signed(who),
Box::new(pezframe_system::RawOrigin::Root.into()),
set_balance_proposal_bounded(value),
DispatchTime::After(delay),
)
}
pub fn next_block() {
System::set_block_number(System::block_number() + 1);
Scheduler::on_initialize(System::block_number());
}
pub fn run_to(n: u64) {
while System::block_number() < n {
next_block();
}
}
#[allow(dead_code)]
pub fn begin_referendum() -> ReferendumIndex {
System::set_block_number(0);
assert_ok!(propose_set_balance(1, 2, 1));
run_to(2);
0
}
#[allow(dead_code)]
pub fn tally(r: ReferendumIndex) -> Tally {
Referenda::ensure_ongoing(r).unwrap().tally
}
pub fn set_tally(index: ReferendumIndex, ayes: u32, nays: u32) {
<Referenda as Polling<Tally>>::access_poll(index, |status| {
let tally = status.ensure_ongoing().unwrap().0;
tally.ayes = ayes;
tally.nays = nays;
});
}
pub fn waiting_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Ongoing(ReferendumStatus { submitted, deciding: None, .. }) => submitted,
_ => panic!("Not waiting"),
}
}
pub fn deciding_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Ongoing(ReferendumStatus {
deciding: Some(DecidingStatus { since, .. }),
..
}) => since,
_ => panic!("Not deciding"),
}
}
pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Ongoing(ReferendumStatus {
deciding: Some(DecidingStatus { since, confirming: None, .. }),
..
}) => since,
_ => panic!("Not deciding"),
}
}
pub fn confirming_until(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Ongoing(ReferendumStatus {
deciding: Some(DecidingStatus { confirming: Some(until), .. }),
..
}) => until,
_ => panic!("Not confirming"),
}
}
pub fn approved_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Approved(since, ..) => since,
_ => panic!("Not approved"),
}
}
pub fn rejected_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Rejected(since, ..) => since,
_ => panic!("Not rejected"),
}
}
pub fn cancelled_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Cancelled(since, ..) => since,
_ => panic!("Not cancelled"),
}
}
pub fn killed_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::Killed(since, ..) => since,
_ => panic!("Not killed"),
}
}
pub fn timed_out_since(i: ReferendumIndex) -> u64 {
match ReferendumInfoFor::<Test>::get(i).unwrap() {
ReferendumInfo::TimedOut(since, ..) => since,
_ => panic!("Not timed out"),
}
}
fn is_deciding(i: ReferendumIndex) -> bool {
matches!(
ReferendumInfoFor::<Test>::get(i),
Some(ReferendumInfo::Ongoing(ReferendumStatus { deciding: Some(_), .. }))
)
}
#[derive(Clone, Copy)]
pub enum RefState {
Failing,
Passing,
Confirming { immediate: bool },
}
impl RefState {
pub fn create(self) -> ReferendumIndex {
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(pezframe_support::dispatch::RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(10),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
if matches!(self, RefState::Confirming { immediate: true }) {
set_tally(0, 100, 0);
}
let index = ReferendumCount::<Test>::get() - 1;
while !is_deciding(index) {
run_to(System::block_number() + 1);
}
if matches!(self, RefState::Confirming { immediate: false }) {
set_tally(0, 100, 0);
run_to(System::block_number() + 1);
}
if matches!(self, RefState::Confirming { .. }) {
assert_eq!(confirming_until(index), System::block_number() + 2);
}
if matches!(self, RefState::Passing) {
set_tally(0, 100, 99);
run_to(System::block_number() + 1);
}
index
}
}
/// note a new preimage without registering.
pub fn note_preimage(who: u64) -> <Test as pezframe_system::Config>::Hash {
use std::sync::atomic::{AtomicU8, Ordering};
// note a new preimage on every function invoke.
static COUNTER: AtomicU8 = AtomicU8::new(0);
let data = vec![COUNTER.fetch_add(1, Ordering::Relaxed)];
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(who), data.clone()));
let hash = BlakeTwo256::hash(&data);
assert!(!Preimage::is_requested(&hash));
hash
}
+709
View File
@@ -0,0 +1,709 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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.
//! The crate's tests.
use super::*;
use crate::mock::{RefState::*, *};
use assert_matches::assert_matches;
use codec::Decode;
use pezframe_support::{assert_noop, assert_ok, dispatch::RawOrigin, traits::Contains};
use pezpallet_balances::Error as BalancesError;
use pezsp_runtime::DispatchError::BadOrigin;
#[test]
fn params_should_work() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(ReferendumCount::<Test>::get(), 0);
assert_eq!(Balances::free_balance(42), 0);
assert_eq!(pezpallet_balances::TotalIssuance::<Test>::get(), 600);
});
}
#[test]
fn basic_happy_path_works() {
ExtBuilder::default().build_and_execute(|| {
// #1: submit
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(10),
));
assert_eq!(Balances::reserved_balance(&1), 2);
assert_eq!(ReferendumCount::<Test>::get(), 1);
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
run_to(4);
assert_eq!(DecidingCount::<Test>::get(0), 0);
run_to(5);
// #5: 4 blocks after submit - vote should now be deciding.
assert_eq!(DecidingCount::<Test>::get(0), 1);
run_to(6);
// #6: Lots of ayes. Should now be confirming.
set_tally(0, 100, 0);
run_to(7);
assert_eq!(confirming_until(0), 9);
run_to(9);
// #8: Should be confirmed & ended.
assert_eq!(approved_since(0), 9);
assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(2), 0));
run_to(12);
// #9: Should not yet be enacted.
assert_eq!(Balances::free_balance(&42), 0);
run_to(13);
// #10: Proposal should be executed.
assert_eq!(Balances::free_balance(&42), 1);
});
}
#[test]
fn insta_confirm_then_kill_works() {
ExtBuilder::default().build_and_execute(|| {
let r = Confirming { immediate: true }.create();
run_to(6);
assert_ok!(Referenda::kill(RuntimeOrigin::root(), r));
assert_eq!(killed_since(r), 6);
});
}
#[test]
fn confirm_then_reconfirm_with_elapsed_trigger_works() {
ExtBuilder::default().build_and_execute(|| {
let r = Confirming { immediate: false }.create();
assert_eq!(confirming_until(r), 8);
run_to(7);
set_tally(r, 100, 99);
run_to(8);
assert_eq!(deciding_and_failing_since(r), 5);
run_to(11);
assert_eq!(approved_since(r), 11);
});
}
#[test]
fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() {
ExtBuilder::default().build_and_execute(|| {
let r = Confirming { immediate: true }.create();
run_to(6);
assert_eq!(confirming_until(r), 7);
set_tally(r, 100, 99);
run_to(7);
assert_eq!(deciding_and_failing_since(r), 5);
run_to(11);
assert_eq!(approved_since(r), 11);
});
}
#[test]
fn instaconfirm_then_reconfirm_with_voting_trigger_works() {
ExtBuilder::default().build_and_execute(|| {
let r = Confirming { immediate: true }.create();
run_to(6);
assert_eq!(confirming_until(r), 7);
set_tally(r, 100, 99);
run_to(7);
assert_eq!(deciding_and_failing_since(r), 5);
run_to(8);
set_tally(r, 100, 0);
run_to(9);
assert_eq!(confirming_until(r), 11);
run_to(11);
assert_eq!(approved_since(r), 11);
});
}
#[test]
fn voting_should_extend_for_late_confirmation() {
ExtBuilder::default().build_and_execute(|| {
let r = Passing.create();
run_to(10);
assert_eq!(confirming_until(r), 11);
run_to(11);
assert_eq!(approved_since(r), 11);
});
}
#[test]
fn should_instafail_during_extension_confirmation() {
ExtBuilder::default().build_and_execute(|| {
let r = Passing.create();
run_to(10);
assert_eq!(confirming_until(r), 11);
// Should insta-fail since it's now past the normal voting time.
set_tally(r, 100, 101);
run_to(11);
assert_eq!(rejected_since(r), 11);
});
}
#[test]
fn confirming_then_fail_works() {
ExtBuilder::default().build_and_execute(|| {
let r = Failing.create();
// Normally ends at 5 + 4 (voting period) = 9.
assert_eq!(deciding_and_failing_since(r), 5);
set_tally(r, 100, 0);
run_to(6);
assert_eq!(confirming_until(r), 8);
set_tally(r, 100, 101);
run_to(9);
assert_eq!(rejected_since(r), 9);
});
}
#[test]
fn queueing_works() {
ExtBuilder::default().build_and_execute(|| {
// Submit a proposal into a track with a queue len of 1.
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(5),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(0),
DispatchTime::After(0),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(5), 0));
run_to(2);
// Submit 3 more proposals into the same queue.
for i in 1..=4 {
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(i),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(i),
DispatchTime::After(0),
));
}
for i in [1, 2, 4] {
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(i), i as u32));
// TODO: decision deposit after some initial votes with a non-highest voted coming
// first.
}
assert_eq!(ReferendumCount::<Test>::get(), 5);
run_to(5);
// One should be being decided.
assert_eq!(DecidingCount::<Test>::get(0), 1);
assert_eq!(deciding_and_failing_since(0), 5);
for i in 1..=4 {
assert_eq!(waiting_since(i), 2);
}
// Vote to set order.
set_tally(1, 1, 10);
set_tally(2, 2, 20);
set_tally(3, 3, 30);
set_tally(4, 100, 0);
println!("Agenda #6: {:?}", pezpallet_scheduler::Agenda::<Test>::get(6));
run_to(6);
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
// Cancel the first.
assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0));
assert_eq!(cancelled_since(0), 6);
// The other with the most approvals (#4) should be being decided.
run_to(7);
assert_eq!(DecidingCount::<Test>::get(0), 1);
assert_eq!(deciding_since(4), 7);
assert_eq!(confirming_until(4), 9);
// Vote on the remaining two to change order.
println!("Set tally #1");
set_tally(1, 30, 31);
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
println!("Set tally #2");
set_tally(2, 20, 20);
println!("{:?}", Vec::<_>::from(TrackQueue::<Test>::get(0)));
// Let confirmation period end.
run_to(9);
// #4 should have been confirmed.
assert_eq!(approved_since(4), 9);
// On to the next block to select the new referendum
run_to(10);
// #1 (the one with the most approvals) should now be being decided.
assert_eq!(deciding_since(1), 10);
// Let it end unsuccessfully.
run_to(14);
assert_eq!(rejected_since(1), 14);
// Service queue.
run_to(15);
// #2 should now be being decided. It will (barely) pass.
assert_eq!(deciding_and_failing_since(2), 15);
// #2 moves into confirming at the last moment with a 50% approval.
run_to(19);
assert_eq!(confirming_until(2), 21);
// #2 gets approved.
run_to(21);
assert_eq!(approved_since(2), 21);
// The final one has since timed out.
run_to(22);
assert_eq!(timed_out_since(3), 22);
});
}
#[test]
fn alarm_interval_works() {
ExtBuilder::default().build_and_execute(|| {
let call =
<Test as Config>::Preimages::bound(CallOf::<Test, ()>::from(Call::nudge_referendum {
index: 0,
}))
.unwrap();
for n in 0..10 {
let interval = n * n;
let now = 100 * (interval + 1);
System::set_block_number(now);
AlarmInterval::set(interval);
let when = now + 1;
let (actual, _) = Referenda::set_alarm(call.clone(), when).unwrap();
assert!(actual >= when);
assert!(actual - interval <= when);
}
});
}
#[test]
fn decision_time_is_correct() {
ExtBuilder::default().build_and_execute(|| {
let decision_time = |since: u64| {
let track = TestTracksInfo::tracks().next().unwrap();
Pallet::<Test>::decision_time(
&DecidingStatus { since: since.into(), confirming: None },
&Tally { ayes: 100, nays: 5 },
track.id,
&track.info,
)
};
for i in 0u64..=100 {
assert!(decision_time(i) > i, "The decision time should be delayed by the curve");
}
});
}
#[test]
fn auto_timeout_should_happen_with_nothing_but_submit() {
ExtBuilder::default().build_and_execute(|| {
// #1: submit
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(20),
));
run_to(20);
assert_matches!(ReferendumInfoFor::<Test>::get(0), Some(ReferendumInfo::Ongoing(..)));
run_to(21);
// #11: Timed out - ended.
assert_matches!(
ReferendumInfoFor::<Test>::get(0),
Some(ReferendumInfo::TimedOut(21, _, None))
);
});
}
#[test]
fn tracks_are_distinguished() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(10),
));
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(2),
Box::new(RawOrigin::None.into()),
set_balance_proposal_bounded(2),
DispatchTime::At(20),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(3), 0));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(4), 1));
let mut i = ReferendumInfoFor::<Test>::iter().collect::<Vec<_>>();
i.sort_by_key(|x| x.0);
assert_eq!(
i,
vec![
(
0,
ReferendumInfo::Ongoing(ReferendumStatus {
track: 0,
origin: OriginCaller::system(RawOrigin::Root),
proposal: set_balance_proposal_bounded(1),
enactment: DispatchTime::At(10),
submitted: 1,
submission_deposit: Deposit { who: 1, amount: 2 },
decision_deposit: Some(Deposit { who: 3, amount: 10 }),
deciding: None,
tally: Tally { ayes: 0, nays: 0 },
in_queue: false,
alarm: Some((5, (5, 0))),
})
),
(
1,
ReferendumInfo::Ongoing(ReferendumStatus {
track: 1,
origin: OriginCaller::system(RawOrigin::None),
proposal: set_balance_proposal_bounded(2),
enactment: DispatchTime::At(20),
submitted: 1,
submission_deposit: Deposit { who: 2, amount: 2 },
decision_deposit: Some(Deposit { who: 4, amount: 1 }),
deciding: None,
tally: Tally { ayes: 0, nays: 0 },
in_queue: false,
alarm: Some((3, (3, 0))),
})
),
]
);
});
}
#[test]
fn submit_errors_work() {
ExtBuilder::default().build_and_execute(|| {
let h = set_balance_proposal_bounded(1);
// No track for Signed origins.
assert_noop!(
Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Signed(2).into()),
h.clone(),
DispatchTime::At(10),
),
Error::<Test>::NoTrack
);
// No funds for deposit
assert_noop!(
Referenda::submit(
RuntimeOrigin::signed(10),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
),
BalancesError::<Test>::InsufficientBalance
);
});
}
#[test]
fn decision_deposit_errors_work() {
ExtBuilder::default().build_and_execute(|| {
let e = Error::<Test>::NotOngoing;
assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0), e);
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
let e = BalancesError::<Test>::InsufficientBalance;
assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(10), 0), e);
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
let e = Error::<Test>::HasDeposit;
assert_noop!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0), e);
});
}
#[test]
fn refund_deposit_works() {
ExtBuilder::default().build_and_execute(|| {
let e = Error::<Test>::BadReferendum;
assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(1), 0), e);
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
let e = Error::<Test>::NoDeposit;
assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(2), 0), e);
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
let e = Error::<Test>::Unfinished;
assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0), e);
run_to(11);
assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0));
});
}
#[test]
fn refund_submission_deposit_works() {
ExtBuilder::default().build_and_execute(|| {
// refund of non existing referendum fails.
let e = Error::<Test>::BadReferendum;
assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(1), 0), e);
// create a referendum.
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h.clone(),
DispatchTime::At(10),
));
// refund of an ongoing referendum fails.
let e = Error::<Test>::BadStatus;
assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0), e);
// cancel referendum.
assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0));
// refund of canceled referendum works.
assert_ok!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(3), 0));
// fails if already refunded.
let e = Error::<Test>::NoDeposit;
assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e);
// create second referendum.
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
// refund of a killed referendum fails.
assert_ok!(Referenda::kill(RuntimeOrigin::root(), 1));
let e = Error::<Test>::NoDeposit;
assert_noop!(Referenda::refund_submission_deposit(RuntimeOrigin::signed(2), 0), e);
});
}
#[test]
fn cancel_works() {
ExtBuilder::default().build_and_execute(|| {
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
run_to(8);
assert_ok!(Referenda::cancel(RuntimeOrigin::signed(4), 0));
assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0));
assert_eq!(cancelled_since(0), 8);
});
}
#[test]
fn cancel_errors_works() {
ExtBuilder::default().build_and_execute(|| {
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
assert_noop!(Referenda::cancel(RuntimeOrigin::signed(1), 0), BadOrigin);
run_to(11);
assert_noop!(Referenda::cancel(RuntimeOrigin::signed(4), 0), Error::<Test>::NotOngoing);
});
}
#[test]
fn kill_works() {
ExtBuilder::default().build_and_execute(|| {
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
run_to(8);
assert_ok!(Referenda::kill(RuntimeOrigin::root(), 0));
let e = Error::<Test>::NoDeposit;
assert_noop!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(3), 0), e);
assert_eq!(killed_since(0), 8);
});
}
#[test]
fn kill_errors_works() {
ExtBuilder::default().build_and_execute(|| {
let h = set_balance_proposal_bounded(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
h,
DispatchTime::At(10),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0));
assert_noop!(Referenda::kill(RuntimeOrigin::signed(4), 0), BadOrigin);
run_to(11);
assert_noop!(Referenda::kill(RuntimeOrigin::root(), 0), Error::<Test>::NotOngoing);
});
}
#[test]
fn set_balance_proposal_is_correctly_filtered_out() {
for i in 0..10 {
let call = crate::mock::RuntimeCall::decode(&mut &set_balance_proposal(i)[..]).unwrap();
assert!(!<Test as pezframe_system::Config>::BaseCallFilter::contains(&call));
}
}
#[test]
fn curve_handles_all_inputs() {
let test_curve = Curve::LinearDecreasing {
length: Perbill::one(),
floor: Perbill::zero(),
ceil: Perbill::from_percent(100),
};
let delay = test_curve.delay(Perbill::zero());
assert_eq!(delay, Perbill::one());
let threshold = test_curve.threshold(Perbill::one());
assert_eq!(threshold, Perbill::zero());
}
#[test]
fn set_metadata_works() {
ExtBuilder::default().build_and_execute(|| {
// invalid preimage hash.
let invalid_hash: <Test as pezframe_system::Config>::Hash = [1u8; 32].into();
// fails to set metadata for a finished referendum.
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(1),
));
let index = ReferendumCount::<Test>::get() - 1;
assert_ok!(Referenda::kill(RuntimeOrigin::root(), index));
assert_noop!(
Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(invalid_hash)),
Error::<Test>::NotOngoing,
);
// no permission to set metadata.
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(1),
));
let index = ReferendumCount::<Test>::get() - 1;
assert_noop!(
Referenda::set_metadata(RuntimeOrigin::signed(2), index, Some(invalid_hash)),
Error::<Test>::NoPermission,
);
// preimage does not exist.
let index = ReferendumCount::<Test>::get() - 1;
assert_noop!(
Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(invalid_hash)),
Error::<Test>::PreimageNotExist,
);
// metadata set.
let index = ReferendumCount::<Test>::get() - 1;
let hash = note_preimage(1);
assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(hash)));
System::assert_last_event(RuntimeEvent::Referenda(crate::Event::MetadataSet {
index,
hash,
}));
});
}
#[test]
fn clear_metadata_works() {
ExtBuilder::default().build_and_execute(|| {
let hash = note_preimage(1);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
set_balance_proposal_bounded(1),
DispatchTime::At(1),
));
let index = ReferendumCount::<Test>::get() - 1;
assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(hash)));
assert_noop!(
Referenda::set_metadata(RuntimeOrigin::signed(2), index, None),
Error::<Test>::NoPermission,
);
assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, None),);
System::assert_last_event(RuntimeEvent::Referenda(crate::Event::MetadataCleared {
index,
hash,
}));
});
}
#[test]
fn detects_incorrect_len() {
ExtBuilder::default().build_and_execute(|| {
let hash = note_preimage(1);
assert_noop!(
Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Root.into()),
pezframe_support::traits::Bounded::Lookup { hash, len: 3 },
DispatchTime::At(1),
),
Error::<Test>::PreimageStoredWithDifferentLength
);
});
}
/// Ensures that `DispatchTime::After(0)` plus `min_enactment_period = 0` works.
#[test]
fn zero_enactment_delay_executes_proposal_at_next_block() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Balances::free_balance(42), 0);
assert_ok!(Referenda::submit(
RuntimeOrigin::signed(1),
Box::new(RawOrigin::Signed(1).into()),
Preimage::bound(
pezpallet_balances::Call::transfer_keep_alive { dest: 42, value: 20 }.into()
)
.unwrap(),
DispatchTime::After(0),
));
assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(1), 0));
assert_eq!(ReferendumCount::<Test>::get(), 1);
set_tally(0, 100, 0);
run_to(9);
assert_eq!(Balances::free_balance(42), 20);
});
}
+902
View File
@@ -0,0 +1,902 @@
// This file is part of Bizinikiwi.
// Copyright (C) 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.
//! Miscellaneous additional datatypes.
use super::*;
use alloc::borrow::Cow;
use codec::{Compact, Decode, DecodeWithMemTracking, Encode, EncodeLike, Input, MaxEncodedLen};
use core::fmt::Debug;
use pezframe_support::{
traits::{schedule::v3::Anon, Bounded},
Parameter,
};
use scale_info::{Type, TypeInfo};
use pezsp_arithmetic::{Rounding::*, SignedRounding::*};
use pezsp_runtime::{FixedI64, PerThing, RuntimeDebug};
pub type BalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
pub type BlockNumberFor<T, I> =
<<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
pub type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
<T as pezframe_system::Config>::AccountId,
>>::NegativeImbalance;
pub type CallOf<T, I> = <T as Config<I>>::RuntimeCall;
pub type BoundedCallOf<T, I> =
Bounded<<T as Config<I>>::RuntimeCall, <T as pezframe_system::Config>::Hashing>;
pub type VotesOf<T, I> = <T as Config<I>>::Votes;
pub type TallyOf<T, I> = <T as Config<I>>::Tally;
pub type PalletsOriginOf<T> =
<<T as pezframe_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
pub type ReferendumInfoOf<T, I> = ReferendumInfo<
TrackIdOf<T, I>,
PalletsOriginOf<T>,
BlockNumberFor<T, I>,
BoundedCallOf<T, I>,
BalanceOf<T, I>,
TallyOf<T, I>,
<T as pezframe_system::Config>::AccountId,
ScheduleAddressOf<T, I>,
>;
pub type ReferendumStatusOf<T, I> = ReferendumStatus<
TrackIdOf<T, I>,
PalletsOriginOf<T>,
BlockNumberFor<T, I>,
BoundedCallOf<T, I>,
BalanceOf<T, I>,
TallyOf<T, I>,
<T as pezframe_system::Config>::AccountId,
ScheduleAddressOf<T, I>,
>;
pub type DecidingStatusOf<T, I> = DecidingStatus<BlockNumberFor<T, I>>;
pub type TrackInfoOf<T, I = ()> = TrackInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>;
pub type TrackIdOf<T, I> =
<<T as Config<I>>::Tracks as TracksInfo<BalanceOf<T, I>, BlockNumberFor<T, I>>>::Id;
pub type ScheduleAddressOf<T, I> = <<T as Config<I>>::Scheduler as Anon<
BlockNumberFor<T, I>,
CallOf<T, I>,
PalletsOriginOf<T>,
>>::Address;
/// A referendum index.
pub type ReferendumIndex = u32;
pub trait InsertSorted<T> {
/// Inserts an item into a sorted series.
///
/// Returns `true` if it was inserted, `false` if it would belong beyond the bound of the
/// series.
fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
&mut self,
t: T,
f: F,
) -> bool;
}
impl<T: Ord, S: Get<u32>> InsertSorted<T> for BoundedVec<T, S> {
fn insert_sorted_by_key<F: FnMut(&T) -> K, K: PartialOrd<K> + Ord>(
&mut self,
t: T,
mut f: F,
) -> bool {
let index = self.binary_search_by_key::<K, F>(&f(&t), f).unwrap_or_else(|x| x);
self.force_insert_keep_right(index, t).is_ok()
}
}
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct DecidingStatus<BlockNumber> {
/// When this referendum began being "decided". If confirming, then the
/// end will actually be delayed until the end of the confirmation period.
pub since: BlockNumber,
/// If `Some`, then the referendum has entered confirmation stage and will end at
/// the block number as long as it doesn't lose its approval in the meantime.
pub confirming: Option<BlockNumber>,
}
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct Deposit<AccountId, Balance> {
pub who: AccountId,
pub amount: Balance,
}
pub const DEFAULT_MAX_TRACK_NAME_LEN: usize = 25;
/// Helper structure to treat a `[u8; N]` array as a string.
///
/// This is a temporary fix (see [#7671](https://github.com/pezkuwichain/kurdistan-sdk/issues/126)) in
/// order to stop `pezkuwi.js` apps to fail when trying to decode the `name` field in `TrackInfo`.
#[derive(Clone, Eq, DecodeWithMemTracking, PartialEq, Debug)]
pub struct StringLike<const N: usize>(pub [u8; N]);
impl<const N: usize> TypeInfo for StringLike<N> {
type Identity = <&'static str as TypeInfo>::Identity;
fn type_info() -> Type {
<&str as TypeInfo>::type_info()
}
}
impl<const N: usize> MaxEncodedLen for StringLike<N> {
fn max_encoded_len() -> usize {
<Compact<u32> as MaxEncodedLen>::max_encoded_len().saturating_add(N)
}
}
impl<const N: usize> Encode for StringLike<N> {
fn encode(&self) -> Vec<u8> {
use codec::Compact;
(Compact(N as u32), self.0).encode()
}
}
impl<const N: usize> Decode for StringLike<N> {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
let Compact(size): Compact<u32> = Decode::decode(input)?;
if size != N as u32 {
return Err("Invalid size".into());
}
let bytes: [u8; N] = Decode::decode(input)?;
Ok(Self(bytes))
}
}
/// Detailed information about the configuration of a referenda track. Used for internal storage.
pub type TrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
TrackDetails<Balance, Moment, [u8; N]>;
/// Detailed information about the configuration of a referenda track. Used for const querying.
pub type ConstTrackInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> =
TrackDetails<Balance, Moment, StringLike<N>>;
/// Detailed information about the configuration of a referenda track
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
)]
pub struct TrackDetails<Balance, Moment, Name> {
/// Name of this track.
pub name: Name,
/// A limit for the number of referenda on this track that can be being decided at once.
/// For Root origin this should generally be just one.
pub max_deciding: u32,
/// Amount that must be placed on deposit before a decision can be made.
pub decision_deposit: Balance,
/// Amount of time this must be submitted for before a decision can be made.
pub prepare_period: Moment,
/// Amount of time that a decision may take to be approved prior to cancellation.
pub decision_period: Moment,
/// Amount of time that the approval criteria must hold before it can be approved.
pub confirm_period: Moment,
/// Minimum amount of time that an approved proposal must be in the dispatch queue.
pub min_enactment_period: Moment,
/// Minimum aye votes as percentage of overall conviction-weighted votes needed for
/// approval as a function of time into decision period.
pub min_approval: Curve,
/// Minimum pre-conviction aye-votes ("support") as percentage of overall population that is
/// needed for approval as a function of time into decision period.
pub min_support: Curve,
}
/// Track groups the information of a voting track with its corresponding identifier
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Eq, PartialEq, Debug,
)]
pub struct Track<Id, Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN> {
pub id: Id,
pub info: TrackInfo<Balance, Moment, N>,
}
/// Information on the voting tracks.
pub trait TracksInfo<Balance, Moment, const N: usize = DEFAULT_MAX_TRACK_NAME_LEN>
where
Balance: Clone + Debug + Eq + 'static,
Moment: Clone + Debug + Eq + 'static,
{
/// The identifier for a track.
type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen;
/// The origin type from which a track is implied.
type RuntimeOrigin;
/// Return the sorted iterable list of known tracks and their information.
///
/// The iterator MUST be sorted by `Id`. Consumers of this trait are advised to assert
/// [`Self::check_integrity`] prior to any use.
fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, Balance, Moment, N>>>;
/// Determine the voting track for the given `origin`.
fn track_for(origin: &Self::RuntimeOrigin) -> Result<Self::Id, ()>;
/// Return the list of identifiers of the known tracks.
fn track_ids() -> impl Iterator<Item = Self::Id> {
Self::tracks().map(|x| x.id)
}
/// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`.
fn info(id: Self::Id) -> Option<Cow<'static, TrackInfo<Balance, Moment, N>>> {
Self::tracks().find(|x| x.id == id).map(|t| match t {
Cow::Borrowed(x) => Cow::Borrowed(&x.info),
Cow::Owned(x) => Cow::Owned(x.info),
})
}
/// Check assumptions about the static data that this trait provides.
fn check_integrity() -> Result<(), &'static str> {
use core::cmp::Ordering;
// Adapted from Iterator::is_sorted implementation available in nightly
// https://github.com/rust-lang/rust/issues/53485
let mut iter = Self::tracks();
let mut last = match iter.next() {
Some(ref e) => e.id,
None => return Ok(()),
};
iter.all(|curr| {
let curr = curr.as_ref().id;
if let Ordering::Greater = last.cmp(&curr) {
return false;
}
last = curr;
true
})
.then_some(())
.ok_or("The tracks that were returned by `tracks` were not sorted by `Id`")
}
}
/// Info regarding an ongoing referendum.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct ReferendumStatus<
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
> {
/// The track of this referendum.
pub track: TrackId,
/// The origin for this referendum.
pub origin: RuntimeOrigin,
/// The hash of the proposal up for referendum.
pub proposal: Call,
/// The time the proposal should be scheduled for enactment.
pub enactment: DispatchTime<Moment>,
/// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if
/// `deciding` is `None`.
pub submitted: Moment,
/// The deposit reserved for the submission of this referendum.
pub submission_deposit: Deposit<AccountId, Balance>,
/// The deposit reserved for this referendum to be decided.
pub decision_deposit: Option<Deposit<AccountId, Balance>>,
/// The status of a decision being made. If `None`, it has not entered the deciding period.
pub deciding: Option<DecidingStatus<Moment>>,
/// The current tally of votes in this referendum.
pub tally: Tally,
/// Whether we have been placed in the queue for being decided or not.
pub in_queue: bool,
/// The next scheduled wake-up, if `Some`.
pub alarm: Option<(Moment, ScheduleAddress)>,
}
/// Info regarding a referendum, present or past.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Clone,
PartialEq,
Eq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub enum ReferendumInfo<
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
> {
/// Referendum has been submitted and is being voted on.
Ongoing(
ReferendumStatus<
TrackId,
RuntimeOrigin,
Moment,
Call,
Balance,
Tally,
AccountId,
ScheduleAddress,
>,
),
/// Referendum finished with approval. Submission deposit is held.
Approved(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with rejection. Submission deposit is held.
Rejected(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with cancellation. Submission deposit is held.
Cancelled(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished and was never decided. Submission deposit is held.
TimedOut(Moment, Option<Deposit<AccountId, Balance>>, Option<Deposit<AccountId, Balance>>),
/// Referendum finished with a kill.
Killed(Moment),
}
impl<
TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
>
ReferendumInfo<TrackId, RuntimeOrigin, Moment, Call, Balance, Tally, AccountId, ScheduleAddress>
{
/// Take the Decision Deposit from `self`, if there is one. Returns an `Err` if `self` is not
/// in a valid state for the Decision Deposit to be refunded.
pub fn take_decision_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
use ReferendumInfo::*;
match self {
Ongoing(x) if x.decision_deposit.is_none() => Ok(None),
// Cannot refund deposit if Ongoing as this breaks assumptions.
Ongoing(_) => Err(()),
Approved(_, _, d) | Rejected(_, _, d) | TimedOut(_, _, d) | Cancelled(_, _, d) =>
Ok(d.take()),
Killed(_) => Ok(None),
}
}
/// Take the Submission Deposit from `self`, if there is one and it's in a valid state to be
/// taken. Returns an `Err` if `self` is not in a valid state for the Submission Deposit to be
/// refunded.
pub fn take_submission_deposit(&mut self) -> Result<Option<Deposit<AccountId, Balance>>, ()> {
use ReferendumInfo::*;
match self {
// Can only refund deposit if it's approved or cancelled.
Approved(_, s, _) | Cancelled(_, s, _) => Ok(s.take()),
// Cannot refund deposit if Ongoing as this breaks assumptions.
Ongoing(..) | Rejected(..) | TimedOut(..) | Killed(..) => Err(()),
}
}
}
/// Type for describing a curve over the 2-dimensional space of axes between 0-1, as represented
/// by `(Perbill, Perbill)`.
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen)]
#[cfg_attr(not(feature = "std"), derive(RuntimeDebug))]
pub enum Curve {
/// Linear curve starting at `(0, ceil)`, proceeding linearly to `(length, floor)`, then
/// remaining at `floor` until the end of the period.
LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill },
/// Stepped curve, beginning at `(0, begin)`, then remaining constant for `period`, at which
/// point it steps down to `(period, begin - step)`. It then remains constant for another
/// `period` before stepping down to `(period * 2, begin - step * 2)`. This pattern continues
/// but the `y` component has a lower limit of `end`.
SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill },
/// A recipocal (`K/(x+S)-T`) curve: `factor` is `K` and `x_offset` is `S`, `y_offset` is `T`.
Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 },
}
/// Calculate the quadratic solution for the given curve.
///
/// WARNING: This is a `const` function designed for convenient use at build time and
/// will panic on overflow. Ensure that any inputs are sensible.
const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 {
const TWO: FixedI64 = FixedI64::from_u32(2);
const FOUR: FixedI64 = FixedI64::from_u32(4);
b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a))
}
impl Curve {
/// Create a `Curve::Linear` instance from a high-level description.
///
/// WARNING: This is a `const` function designed for convenient use at build time and
/// will panic on overflow. Ensure that any inputs are sensible.
pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve {
let length = FixedI64::from_rational(length, period).into_perbill();
let floor = floor.into_perbill();
let ceil = ceil.into_perbill();
Curve::LinearDecreasing { length, floor, ceil }
}
/// Create a `Curve::Reciprocal` instance from a high-level description.
///
/// WARNING: This is a `const` function designed for convenient use at build time and
/// will panic on overflow. Ensure that any inputs are sensible.
pub const fn make_reciprocal(
delay: u128,
period: u128,
level: FixedI64,
floor: FixedI64,
ceil: FixedI64,
) -> Curve {
let delay = FixedI64::from_rational(delay, period).into_perbill();
let mut bounds = (
(
FixedI64::from_u32(0),
Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil),
FixedI64::from_inner(i64::max_value()),
),
(
FixedI64::from_u32(1),
Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil),
FixedI64::from_inner(i64::max_value()),
),
);
const TWO: FixedI64 = FixedI64::from_u32(2);
while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 {
let factor = (bounds.0).0.add((bounds.1).0).div(TWO);
let curve = Self::reciprocal_from_parts(factor, floor, ceil);
let curve_level = FixedI64::from_perbill(curve.const_threshold(delay));
if curve_level.into_inner() > level.into_inner() {
bounds = (bounds.0, (factor, curve, curve_level.sub(level)));
} else {
bounds = ((factor, curve, level.sub(curve_level)), bounds.1);
}
}
if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() {
(bounds.0).1
} else {
(bounds.1).1
}
}
/// Create a `Curve::Reciprocal` instance from basic parameters.
///
/// WARNING: This is a `const` function designed for convenient use at build time and
/// will panic on overflow. Ensure that any inputs are sensible.
const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self {
let delta = ceil.sub(floor);
let x_offset = pos_quad_solution(delta, delta, factor.neg());
let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset)));
Curve::Reciprocal { factor, x_offset, y_offset }
}
/// Print some info on the curve.
#[cfg(feature = "std")]
pub fn info(&self, days: u32, name: impl std::fmt::Display) {
let hours = days * 24;
println!("Curve {} := {:?}:", name, self);
println!(" t + 0h: {:?}", self.threshold(Perbill::zero()));
println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours)));
println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours)));
println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours)));
println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours)));
println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours)));
println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours)));
let mut l = 0;
for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() {
let t = days * n / d;
if t != l {
println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days)));
l = t;
}
}
let t = |p: Perbill| -> std::string::String {
if p.is_one() {
"never".into()
} else {
let minutes = p * (hours * 60);
if minutes < 60 {
format!("{} minutes", minutes)
} else if minutes < 8 * 60 && minutes % 60 != 0 {
format!("{} hours {} minutes", minutes / 60, minutes % 60)
} else if minutes < 72 * 60 {
format!("{} hours", minutes / 60)
} else if minutes / 60 % 24 == 0 {
format!("{} days", minutes / 60 / 24)
} else {
format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24)
}
}
};
if self.delay(Perbill::from_percent(49)) < Perbill::one() {
println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30))));
println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10))));
println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3))));
println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1))));
println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000))));
println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000))));
} else {
println!(
" 99.9% threshold: {}",
t(self.delay(Perbill::from_rational(999u32, 1_000)))
);
println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99))));
println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95))));
println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90))));
println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75))));
println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60))));
}
}
/// Determine the `y` value for the given `x` value.
pub fn threshold(&self, x: Perbill) -> Perbill {
match self {
Self::LinearDecreasing { length, floor, ceil } =>
*ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)),
Self::SteppedDecreasing { begin, end, step, period } =>
(*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end),
Self::Reciprocal { factor, x_offset, y_offset } => factor
.checked_rounding_div(FixedI64::from(x) + *x_offset, Low)
.map(|yp| (yp + *y_offset).into_clamped_perthing())
.unwrap_or_else(Perbill::one),
}
}
/// Determine the `y` value for the given `x` value.
///
/// This is a partial implementation designed only for use in const functions.
const fn const_threshold(&self, x: Perbill) -> Perbill {
match self {
Self::Reciprocal { factor, x_offset, y_offset } => {
match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) {
Some(yp) => (yp.add(*y_offset)).into_perbill(),
None => Perbill::one(),
}
},
_ => panic!("const_threshold cannot be used on this curve"),
}
}
/// Determine the smallest `x` value such that `passing` returns `true` when passed along with
/// the given `y` value.
///
/// If `passing` never returns `true` for any value of `x` when paired with `y`, then
/// `Perbill::one` may be returned.
///
/// ```nocompile
/// let c = Curve::LinearDecreasing { begin: Perbill::one(), delta: Perbill::one() };
/// // ^^^ Can be any curve.
/// let y = Perbill::from_percent(50);
/// // ^^^ Can be any value.
/// let x = c.delay(y);
/// assert!(c.passing(x, y));
/// ```
pub fn delay(&self, y: Perbill) -> Perbill {
match self {
Self::LinearDecreasing { length, floor, ceil } =>
if y < *floor {
Perbill::one()
} else if y > *ceil {
Perbill::zero()
} else {
(*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length)
},
Self::SteppedDecreasing { begin, end, step, period } =>
if y < *end {
Perbill::one()
} else {
period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step))
},
Self::Reciprocal { factor, x_offset, y_offset } => {
let y = FixedI64::from(y);
let maybe_term = factor.checked_rounding_div(y - *y_offset, High);
maybe_term
.and_then(|term| (term - *x_offset).try_into_perthing().ok())
.unwrap_or_else(Perbill::one)
},
}
}
/// Return `true` iff the `y` value is greater than the curve at the `x`.
pub fn passing(&self, x: Perbill, y: Perbill) -> bool {
y >= self.threshold(x)
}
}
#[cfg(feature = "std")]
impl Debug for Curve {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LinearDecreasing { length, floor, ceil } => {
write!(
f,
"Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]",
ceil, length, floor, floor,
)
},
Self::SteppedDecreasing { begin, end, step, period } => {
write!(
f,
"Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]",
begin, end, period, step,
)
},
Self::Reciprocal { factor, x_offset, y_offset } => {
write!(
f,
"Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]",
factor, x_offset, y_offset,
)
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezframe_support::traits::ConstU32;
use pezsp_runtime::{str_array as s, PerThing};
const fn percent(x: u128) -> FixedI64 {
FixedI64::from_rational(x, 100)
}
const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50));
const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50));
const WHITE_APP: Curve =
Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100));
const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50));
const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100));
const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50));
const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100));
const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50));
const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100));
const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50));
const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100));
const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50));
const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100));
const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50));
// TODO: ceil for linear.
#[test]
#[should_panic]
fn check_curves() {
TIP_APP.info(28u32, "Tip Approval");
TIP_SUP.info(28u32, "Tip Support");
ROOT_APP.info(28u32, "Root Approval");
ROOT_SUP.info(28u32, "Root Support");
WHITE_APP.info(28u32, "Whitelist Approval");
WHITE_SUP.info(28u32, "Whitelist Support");
SMALL_APP.info(28u32, "Small Spend Approval");
SMALL_SUP.info(28u32, "Small Spend Support");
MID_APP.info(28u32, "Mid Spend Approval");
MID_SUP.info(28u32, "Mid Spend Support");
BIG_APP.info(28u32, "Big Spend Approval");
BIG_SUP.info(28u32, "Big Spend Support");
HUGE_APP.info(28u32, "Huge Spend Approval");
HUGE_SUP.info(28u32, "Huge Spend Support");
PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval");
PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support");
ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval");
ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support");
assert!(false);
}
#[test]
fn insert_sorted_works() {
let mut b: BoundedVec<u32, ConstU32<6>> = vec![20, 30, 40].try_into().unwrap();
assert!(b.insert_sorted_by_key(10, |&x| x));
assert_eq!(&b[..], &[10, 20, 30, 40][..]);
assert!(b.insert_sorted_by_key(60, |&x| x));
assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]);
assert!(b.insert_sorted_by_key(50, |&x| x));
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
assert!(!b.insert_sorted_by_key(9, |&x| x));
assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]);
assert!(b.insert_sorted_by_key(11, |&x| x));
assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]);
assert!(b.insert_sorted_by_key(21, |&x| x));
assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]);
assert!(b.insert_sorted_by_key(61, |&x| x));
assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]);
assert!(b.insert_sorted_by_key(51, |&x| x));
assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]);
}
#[test]
fn translated_reciprocal_works() {
let c: Curve = Curve::Reciprocal {
factor: FixedI64::from_float(0.03125),
x_offset: FixedI64::from_float(0.0363306838226),
y_offset: FixedI64::from_float(0.139845532427),
};
c.info(28u32, "Test");
for i in 0..9_696_969u32 {
let query = Perbill::from_rational(i, 9_696_969);
// Determine the nearest point in time when the query will be above threshold.
let delay_needed = c.delay(query);
// Ensure that it actually does pass at that time, or that it will never pass.
assert!(delay_needed.is_one() || c.passing(delay_needed, query));
}
}
#[test]
fn stepped_decreasing_works() {
fn pc(x: u32) -> Perbill {
Perbill::from_percent(x)
}
let c =
Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) };
for i in 0..9_696_969u32 {
let query = Perbill::from_rational(i, 9_696_969);
// Determine the nearest point in time when the query will be above threshold.
let delay_needed = c.delay(query);
// Ensure that it actually does pass at that time, or that it will never pass.
assert!(delay_needed.is_one() || c.passing(delay_needed, query));
}
assert_eq!(c.threshold(pc(0)), pc(80));
assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80));
assert_eq!(c.threshold(pc(15)), pc(70));
assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70));
assert_eq!(c.threshold(pc(30)), pc(60));
assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60));
assert_eq!(c.threshold(pc(45)), pc(50));
assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50));
assert_eq!(c.threshold(pc(60)), pc(40));
assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40));
assert_eq!(c.threshold(pc(75)), pc(30));
assert_eq!(c.threshold(pc(100)), pc(30));
assert_eq!(c.delay(pc(100)), pc(0));
assert_eq!(c.delay(pc(80)), pc(0));
assert_eq!(c.delay(pc(80).less_epsilon()), pc(15));
assert_eq!(c.delay(pc(70)), pc(15));
assert_eq!(c.delay(pc(70).less_epsilon()), pc(30));
assert_eq!(c.delay(pc(60)), pc(30));
assert_eq!(c.delay(pc(60).less_epsilon()), pc(45));
assert_eq!(c.delay(pc(50)), pc(45));
assert_eq!(c.delay(pc(50).less_epsilon()), pc(60));
assert_eq!(c.delay(pc(40)), pc(60));
assert_eq!(c.delay(pc(40).less_epsilon()), pc(75));
assert_eq!(c.delay(pc(30)), pc(75));
assert_eq!(c.delay(pc(30).less_epsilon()), pc(100));
assert_eq!(c.delay(pc(0)), pc(100));
}
#[test]
fn tracks_integrity_check_detects_unsorted() {
use crate::mock::RuntimeOrigin;
pub struct BadTracksInfo;
impl TracksInfo<u64, u64> for BadTracksInfo {
type Id = u8;
type RuntimeOrigin = <RuntimeOrigin as OriginTrait>::PalletsOrigin;
fn tracks() -> impl Iterator<Item = Cow<'static, Track<Self::Id, u64, u64>>> {
static DATA: [Track<u8, u64, u64>; 2] = [
Track {
id: 1u8,
info: TrackInfo {
name: s("root"),
max_deciding: 1,
decision_deposit: 10,
prepare_period: 4,
decision_period: 4,
confirm_period: 2,
min_enactment_period: 4,
min_approval: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(50),
ceil: Perbill::from_percent(100),
},
min_support: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(0),
ceil: Perbill::from_percent(100),
},
},
},
Track {
id: 0u8,
info: TrackInfo {
name: s("none"),
max_deciding: 3,
decision_deposit: 1,
prepare_period: 2,
decision_period: 2,
confirm_period: 1,
min_enactment_period: 2,
min_approval: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(95),
ceil: Perbill::from_percent(100),
},
min_support: Curve::LinearDecreasing {
length: Perbill::from_percent(100),
floor: Perbill::from_percent(90),
ceil: Perbill::from_percent(100),
},
},
},
];
DATA.iter().map(Cow::Borrowed)
}
fn track_for(_: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
unimplemented!()
}
}
assert_eq!(
BadTracksInfo::check_integrity(),
Err("The tracks that were returned by `tracks` were not sorted by `Id`")
);
}
#[test]
fn encoding_and_decoding_of_string_like_structure_works() {
let string_like = StringLike::<13>(*b"hello, world!");
let encoded: Vec<u8> = string_like.encode();
let decoded_as_vec: Vec<u8> =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as Vec<u8> should work");
assert_eq!(decoded_as_vec.len(), 13);
let decoded_as_str: alloc::string::String =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as str should work");
assert_eq!(decoded_as_str.len(), 13);
let decoded_as_string_like: StringLike<13> =
Decode::decode(&mut &encoded.clone()[..]).expect("decoding as StringLike should work");
assert_eq!(decoded_as_string_like.0.len(), 13);
}
}
File diff suppressed because it is too large Load Diff