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:
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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 })
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user