#![recursion_limit = "256"]
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Codec, Encode};
use frame_support::{
dispatch::DispatchResult,
ensure,
traits::{
schedule::{
v3::{Anon as ScheduleAnon, Named as ScheduleNamed},
DispatchTime,
},
Currency, Hash as PreimageHash, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus,
Polling, QueryPreimage, ReservableCurrency, StorePreimage, VoteTally,
},
BoundedVec,
};
use frame_system::pallet_prelude::BlockNumberFor;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Bounded, Dispatchable, One, Saturating, Zero},
DispatchError, Perbill,
};
use sp_std::{fmt::Debug, prelude::*};
mod branch;
pub mod migration;
mod types;
pub mod weights;
use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch};
pub use self::{
pallet::*,
types::{
BalanceOf, BoundedCallOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit,
InsertSorted, NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo,
ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf,
TrackIdOf, TrackInfo, TrackInfoOf, TracksInfo, VotesOf,
},
weights::WeightInfo,
};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub use frame_support::traits::Get;
pub use sp_std::vec::Vec;
#[macro_export]
macro_rules! impl_tracksinfo_get {
($tracksinfo:ty, $balance:ty, $blocknumber:ty) => {
impl
$crate::Get<
$crate::Vec<(
<$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::Id,
$crate::TrackInfo<$balance, $blocknumber>,
)>,
> for $tracksinfo
{
fn get() -> $crate::Vec<(
<$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::Id,
$crate::TrackInfo<$balance, $blocknumber>,
)> {
<$tracksinfo as $crate::TracksInfo<$balance, $blocknumber>>::tracks().to_vec()
}
}
};
}
const ASSEMBLY_ID: LockIdentifier = *b"assembly";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::EnsureOriginWithArg};
use frame_system::pallet_prelude::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(_);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + Sized {
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ From<Call<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>
+ From<frame_system::Call<Self>>;
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
type Scheduler: ScheduleAnon<BlockNumberFor<Self>, CallOf<Self, I>, PalletsOriginOf<Self>>
+ ScheduleNamed<BlockNumberFor<Self>, CallOf<Self, I>, PalletsOriginOf<Self>>;
type Currency: ReservableCurrency<Self::AccountId>;
type SubmitOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
PalletsOriginOf<Self>,
Success = Self::AccountId,
>;
type CancelOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type KillOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type Slash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member + MaxEncodedLen;
type Tally: VoteTally<Self::Votes, TrackIdOf<Self, I>>
+ Clone
+ Codec
+ Eq
+ Debug
+ TypeInfo
+ MaxEncodedLen;
#[pallet::constant]
type SubmissionDeposit: Get<BalanceOf<Self, I>>;
#[pallet::constant]
type MaxQueued: Get<u32>;
#[pallet::constant]
type UndecidingTimeout: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type AlarmInterval: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type Tracks: Get<
Vec<(
<Self::Tracks as TracksInfo<BalanceOf<Self, I>, BlockNumberFor<Self>>>::Id,
TrackInfo<BalanceOf<Self, I>, BlockNumberFor<Self>>,
)>,
> + TracksInfo<
BalanceOf<Self, I>,
BlockNumberFor<Self>,
RuntimeOrigin = <Self::RuntimeOrigin as OriginTrait>::PalletsOrigin,
>;
type Preimages: QueryPreimage + StorePreimage;
}
#[pallet::storage]
pub type ReferendumCount<T, I = ()> = StorageValue<_, ReferendumIndex, ValueQuery>;
#[pallet::storage]
pub type ReferendumInfoFor<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
#[pallet::storage]
pub type TrackQueue<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Twox64Concat,
TrackIdOf<T, I>,
BoundedVec<(ReferendumIndex, T::Votes), T::MaxQueued>,
ValueQuery,
>;
#[pallet::storage]
pub type DecidingCount<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, TrackIdOf<T, I>, u32, ValueQuery>;
#[pallet::storage]
pub type MetadataOf<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, ReferendumIndex, PreimageHash>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Submitted {
index: ReferendumIndex,
track: TrackIdOf<T, I>,
proposal: BoundedCallOf<T, I>,
},
DecisionDepositPlaced {
index: ReferendumIndex,
who: T::AccountId,
amount: BalanceOf<T, I>,
},
DecisionDepositRefunded {
index: ReferendumIndex,
who: T::AccountId,
amount: BalanceOf<T, I>,
},
DepositSlashed {
who: T::AccountId,
amount: BalanceOf<T, I>,
},
DecisionStarted {
index: ReferendumIndex,
track: TrackIdOf<T, I>,
proposal: BoundedCallOf<T, I>,
tally: T::Tally,
},
ConfirmStarted {
index: ReferendumIndex,
},
ConfirmAborted {
index: ReferendumIndex,
},
Confirmed {
index: ReferendumIndex,
tally: T::Tally,
},
Approved {
index: ReferendumIndex,
},
Rejected {
index: ReferendumIndex,
tally: T::Tally,
},
TimedOut {
index: ReferendumIndex,
tally: T::Tally,
},
Cancelled {
index: ReferendumIndex,
tally: T::Tally,
},
Killed {
index: ReferendumIndex,
tally: T::Tally,
},
SubmissionDepositRefunded {
index: ReferendumIndex,
who: T::AccountId,
amount: BalanceOf<T, I>,
},
MetadataSet {
index: ReferendumIndex,
hash: PreimageHash,
},
MetadataCleared {
index: ReferendumIndex,
hash: PreimageHash,
},
}
#[pallet::error]
pub enum Error<T, I = ()> {
NotOngoing,
HasDeposit,
BadTrack,
Full,
QueueEmpty,
BadReferendum,
NothingToDo,
NoTrack,
Unfinished,
NoPermission,
NoDeposit,
BadStatus,
PreimageNotExist,
}
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
Self::do_try_state()?;
Ok(())
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::submit())]
pub fn submit(
origin: OriginFor<T>,
proposal_origin: Box<PalletsOriginOf<T>>,
proposal: BoundedCallOf<T, I>,
enactment_moment: DispatchTime<BlockNumberFor<T>>,
) -> DispatchResult {
let proposal_origin = *proposal_origin;
let who = T::SubmitOrigin::ensure_origin(origin, &proposal_origin)?;
let track =
T::Tracks::track_for(&proposal_origin).map_err(|_| Error::<T, I>::NoTrack)?;
let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?;
let index = ReferendumCount::<T, I>::mutate(|x| {
let r = *x;
*x += 1;
r
});
let now = frame_system::Pallet::<T>::block_number();
let nudge_call =
T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index }))?;
let status = ReferendumStatus {
track,
origin: proposal_origin,
proposal: proposal.clone(),
enactment: enactment_moment,
submitted: now,
submission_deposit,
decision_deposit: None,
deciding: None,
tally: TallyOf::<T, I>::new(track),
in_queue: false,
alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())),
};
ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
Self::deposit_event(Event::<T, I>::Submitted { index, track, proposal });
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(ServiceBranch::max_weight_of_deposit::<T, I>())]
pub fn place_decision_deposit(
origin: OriginFor<T>,
index: ReferendumIndex,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let mut status = Self::ensure_ongoing(index)?;
ensure!(status.decision_deposit.is_none(), Error::<T, I>::HasDeposit);
let track = Self::track(status.track).ok_or(Error::<T, I>::NoTrack)?;
status.decision_deposit =
Some(Self::take_deposit(who.clone(), track.decision_deposit)?);
let now = frame_system::Pallet::<T>::block_number();
let (info, _, branch) = Self::service_referendum(now, index, status);
ReferendumInfoFor::<T, I>::insert(index, info);
let e =
Event::<T, I>::DecisionDepositPlaced { index, who, amount: track.decision_deposit };
Self::deposit_event(e);
Ok(branch.weight_of_deposit::<T, I>().into())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::refund_decision_deposit())]
pub fn refund_decision_deposit(
origin: OriginFor<T>,
index: ReferendumIndex,
) -> DispatchResult {
ensure_signed_or_root(origin)?;
let mut info =
ReferendumInfoFor::<T, I>::get(index).ok_or(Error::<T, I>::BadReferendum)?;
let deposit = info
.take_decision_deposit()
.map_err(|_| Error::<T, I>::Unfinished)?
.ok_or(Error::<T, I>::NoDeposit)?;
Self::refund_deposit(Some(deposit.clone()));
ReferendumInfoFor::<T, I>::insert(index, info);
let e = Event::<T, I>::DecisionDepositRefunded {
index,
who: deposit.who,
amount: deposit.amount,
};
Self::deposit_event(e);
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::cancel())]
pub fn cancel(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
T::CancelOrigin::ensure_origin(origin)?;
let status = Self::ensure_ongoing(index)?;
if let Some((_, last_alarm)) = status.alarm {
let _ = T::Scheduler::cancel(last_alarm);
}
Self::note_one_fewer_deciding(status.track);
Self::deposit_event(Event::<T, I>::Cancelled { index, tally: status.tally });
let info = ReferendumInfo::Cancelled(
frame_system::Pallet::<T>::block_number(),
Some(status.submission_deposit),
status.decision_deposit,
);
ReferendumInfoFor::<T, I>::insert(index, info);
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::kill())]
pub fn kill(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
T::KillOrigin::ensure_origin(origin)?;
let status = Self::ensure_ongoing(index)?;
if let Some((_, last_alarm)) = status.alarm {
let _ = T::Scheduler::cancel(last_alarm);
}
Self::note_one_fewer_deciding(status.track);
Self::deposit_event(Event::<T, I>::Killed { index, tally: status.tally });
Self::slash_deposit(Some(status.submission_deposit.clone()));
Self::slash_deposit(status.decision_deposit.clone());
Self::do_clear_metadata(index);
let info = ReferendumInfo::Killed(frame_system::Pallet::<T>::block_number());
ReferendumInfoFor::<T, I>::insert(index, info);
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(ServiceBranch::max_weight_of_nudge::<T, I>())]
pub fn nudge_referendum(
origin: OriginFor<T>,
index: ReferendumIndex,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let now = frame_system::Pallet::<T>::block_number();
let mut status = Self::ensure_ongoing(index)?;
status.alarm = None;
let (info, dirty, branch) = Self::service_referendum(now, index, status);
if dirty {
ReferendumInfoFor::<T, I>::insert(index, info);
}
Ok(Some(branch.weight_of_nudge::<T, I>()).into())
}
#[pallet::call_index(6)]
#[pallet::weight(OneFewerDecidingBranch::max_weight::<T, I>())]
pub fn one_fewer_deciding(
origin: OriginFor<T>,
track: TrackIdOf<T, I>,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let track_info = T::Tracks::info(track).ok_or(Error::<T, I>::BadTrack)?;
let mut track_queue = TrackQueue::<T, I>::get(track);
let branch =
if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) {
let now = frame_system::Pallet::<T>::block_number();
let (maybe_alarm, branch) =
Self::begin_deciding(&mut status, index, now, track_info);
if let Some(set_alarm) = maybe_alarm {
Self::ensure_alarm_at(&mut status, index, set_alarm);
}
ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
TrackQueue::<T, I>::insert(track, track_queue);
branch.into()
} else {
DecidingCount::<T, I>::mutate(track, |x| x.saturating_dec());
OneFewerDecidingBranch::QueueEmpty
};
Ok(Some(branch.weight::<T, I>()).into())
}
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::refund_submission_deposit())]
pub fn refund_submission_deposit(
origin: OriginFor<T>,
index: ReferendumIndex,
) -> DispatchResult {
ensure_signed_or_root(origin)?;
let mut info =
ReferendumInfoFor::<T, I>::get(index).ok_or(Error::<T, I>::BadReferendum)?;
let deposit = info
.take_submission_deposit()
.map_err(|_| Error::<T, I>::BadStatus)?
.ok_or(Error::<T, I>::NoDeposit)?;
Self::refund_deposit(Some(deposit.clone()));
ReferendumInfoFor::<T, I>::insert(index, info);
let e = Event::<T, I>::SubmissionDepositRefunded {
index,
who: deposit.who,
amount: deposit.amount,
};
Self::deposit_event(e);
Ok(())
}
#[pallet::call_index(8)]
#[pallet::weight(
maybe_hash.map_or(
T::WeightInfo::clear_metadata(), |_| T::WeightInfo::set_some_metadata())
)]
pub fn set_metadata(
origin: OriginFor<T>,
index: ReferendumIndex,
maybe_hash: Option<PreimageHash>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
if let Some(hash) = maybe_hash {
let status = Self::ensure_ongoing(index)?;
ensure!(status.submission_deposit.who == who, Error::<T, I>::NoPermission);
ensure!(T::Preimages::len(&hash).is_some(), Error::<T, I>::PreimageNotExist);
MetadataOf::<T, I>::insert(index, hash);
Self::deposit_event(Event::<T, I>::MetadataSet { index, hash });
Ok(())
} else {
if let Some(status) = Self::ensure_ongoing(index).ok() {
ensure!(status.submission_deposit.who == who, Error::<T, I>::NoPermission);
}
Self::do_clear_metadata(index);
Ok(())
}
}
}
}
impl<T: Config<I>, I: 'static> Polling<T::Tally> for Pallet<T, I> {
type Index = ReferendumIndex;
type Votes = VotesOf<T, I>;
type Moment = BlockNumberFor<T>;
type Class = TrackIdOf<T, I>;
fn classes() -> Vec<Self::Class> {
T::Tracks::tracks().iter().map(|x| x.0).collect()
}
fn access_poll<R>(
index: Self::Index,
f: impl FnOnce(PollStatus<&mut T::Tally, BlockNumberFor<T>, TrackIdOf<T, I>>) -> R,
) -> R {
match ReferendumInfoFor::<T, I>::get(index) {
Some(ReferendumInfo::Ongoing(mut status)) => {
let result = f(PollStatus::Ongoing(&mut status.tally, status.track));
let now = frame_system::Pallet::<T>::block_number();
Self::ensure_alarm_at(&mut status, index, now + One::one());
ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
result
},
Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)),
Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)),
_ => f(PollStatus::None),
}
}
fn try_access_poll<R>(
index: Self::Index,
f: impl FnOnce(
PollStatus<&mut T::Tally, BlockNumberFor<T>, TrackIdOf<T, I>>,
) -> Result<R, DispatchError>,
) -> Result<R, DispatchError> {
match ReferendumInfoFor::<T, I>::get(index) {
Some(ReferendumInfo::Ongoing(mut status)) => {
let result = f(PollStatus::Ongoing(&mut status.tally, status.track))?;
let now = frame_system::Pallet::<T>::block_number();
Self::ensure_alarm_at(&mut status, index, now + One::one());
ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
Ok(result)
},
Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)),
Some(ReferendumInfo::Rejected(end, ..)) => f(PollStatus::Completed(end, false)),
_ => f(PollStatus::None),
}
}
fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf<T, I>)> {
Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track))
}
#[cfg(feature = "runtime-benchmarks")]
fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()> {
let index = ReferendumCount::<T, I>::mutate(|x| {
let r = *x;
*x += 1;
r
});
let now = frame_system::Pallet::<T>::block_number();
let dummy_account_id =
codec::Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::new(&b"dummy"[..]))
.expect("infinite length input; no invalid inputs for type; qed");
let mut status = ReferendumStatusOf::<T, I> {
track: class,
origin: frame_support::dispatch::RawOrigin::Root.into(),
proposal: T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index }))
.map_err(|_| ())?,
enactment: DispatchTime::After(Zero::zero()),
submitted: now,
submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() },
decision_deposit: None,
deciding: None,
tally: TallyOf::<T, I>::new(class),
in_queue: false,
alarm: None,
};
Self::ensure_alarm_at(&mut status, index, sp_runtime::traits::Bounded::max_value());
ReferendumInfoFor::<T, I>::insert(index, ReferendumInfo::Ongoing(status));
Ok(index)
}
#[cfg(feature = "runtime-benchmarks")]
fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> {
let mut status = Self::ensure_ongoing(index).map_err(|_| ())?;
Self::ensure_no_alarm(&mut status);
Self::note_one_fewer_deciding(status.track);
let now = frame_system::Pallet::<T>::block_number();
let info = if approved {
ReferendumInfo::Approved(now, Some(status.submission_deposit), status.decision_deposit)
} else {
ReferendumInfo::Rejected(now, Some(status.submission_deposit), status.decision_deposit)
};
ReferendumInfoFor::<T, I>::insert(index, info);
Ok(())
}
#[cfg(feature = "runtime-benchmarks")]
fn max_ongoing() -> (Self::Class, u32) {
let r = T::Tracks::tracks()
.iter()
.max_by_key(|(_, info)| info.max_deciding)
.expect("Always one class");
(r.0, r.1.max_deciding)
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn ensure_ongoing(
index: ReferendumIndex,
) -> Result<ReferendumStatusOf<T, I>, DispatchError> {
match ReferendumInfoFor::<T, I>::get(index) {
Some(ReferendumInfo::Ongoing(status)) => Ok(status),
_ => Err(Error::<T, I>::NotOngoing.into()),
}
}
pub fn is_referendum_passing(ref_index: ReferendumIndex) -> Result<bool, DispatchError> {
let info = ReferendumInfoFor::<T, I>::get(ref_index).ok_or(Error::<T, I>::BadReferendum)?;
match info {
ReferendumInfo::Ongoing(status) => {
let track = Self::track(status.track).ok_or(Error::<T, I>::NoTrack)?;
let elapsed = if let Some(deciding) = status.deciding {
frame_system::Pallet::<T>::block_number().saturating_sub(deciding.since)
} else {
Zero::zero()
};
Ok(Self::is_passing(
&status.tally,
elapsed,
track.decision_period,
&track.min_support,
&track.min_approval,
status.track,
))
},
_ => Err(Error::<T, I>::NotOngoing.into()),
}
}
fn schedule_enactment(
index: ReferendumIndex,
track: &TrackInfoOf<T, I>,
desired: DispatchTime<BlockNumberFor<T>>,
origin: PalletsOriginOf<T>,
call: BoundedCallOf<T, I>,
) {
let now = frame_system::Pallet::<T>::block_number();
let earliest_allowed = now.saturating_add(track.min_enactment_period);
let desired = desired.evaluate(now);
let ok = T::Scheduler::schedule_named(
(ASSEMBLY_ID, "enactment", index).using_encoded(sp_io::hashing::blake2_256),
DispatchTime::At(desired.max(earliest_allowed)),
None,
63,
origin,
call,
)
.is_ok();
debug_assert!(ok, "LOGIC ERROR: bake_referendum/schedule_named failed");
}
fn set_alarm(
call: BoundedCallOf<T, I>,
when: BlockNumberFor<T>,
) -> Option<(BlockNumberFor<T>, ScheduleAddressOf<T, I>)> {
let alarm_interval = T::AlarmInterval::get().max(One::one());
let when = (when.saturating_add(alarm_interval.saturating_sub(One::one())) /
alarm_interval)
.saturating_mul(alarm_interval);
let result = T::Scheduler::schedule(
DispatchTime::At(when),
None,
128u8,
frame_system::RawOrigin::Root.into(),
call,
);
debug_assert!(
result.is_ok(),
"Unable to schedule a new alarm at #{:?} (now: #{:?}), scheduler error: `{:?}`",
when,
frame_system::Pallet::<T>::block_number(),
result.unwrap_err(),
);
result.ok().map(|x| (when, x))
}
fn begin_deciding(
status: &mut ReferendumStatusOf<T, I>,
index: ReferendumIndex,
now: BlockNumberFor<T>,
track: &TrackInfoOf<T, I>,
) -> (Option<BlockNumberFor<T>>, BeginDecidingBranch) {
let is_passing = Self::is_passing(
&status.tally,
Zero::zero(),
track.decision_period,
&track.min_support,
&track.min_approval,
status.track,
);
status.in_queue = false;
Self::deposit_event(Event::<T, I>::DecisionStarted {
index,
tally: status.tally.clone(),
proposal: status.proposal.clone(),
track: status.track,
});
let confirming = if is_passing {
Self::deposit_event(Event::<T, I>::ConfirmStarted { index });
Some(now.saturating_add(track.confirm_period))
} else {
None
};
let deciding_status = DecidingStatus { since: now, confirming };
let alarm = Self::decision_time(&deciding_status, &status.tally, status.track, track)
.max(now.saturating_add(One::one()));
status.deciding = Some(deciding_status);
let branch =
if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing };
(Some(alarm), branch)
}
fn ready_for_deciding(
now: BlockNumberFor<T>,
track: &TrackInfoOf<T, I>,
index: ReferendumIndex,
status: &mut ReferendumStatusOf<T, I>,
) -> (Option<BlockNumberFor<T>>, ServiceBranch) {
let deciding_count = DecidingCount::<T, I>::get(status.track);
if deciding_count < track.max_deciding {
DecidingCount::<T, I>::insert(status.track, deciding_count.saturating_add(1));
let r = Self::begin_deciding(status, index, now, track);
(r.0, r.1.into())
} else {
let item = (index, status.tally.ayes(status.track));
status.in_queue = true;
TrackQueue::<T, I>::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1));
(None, ServiceBranch::Queued)
}
}
fn next_for_deciding(
track_queue: &mut BoundedVec<(u32, VotesOf<T, I>), T::MaxQueued>,
) -> Option<(ReferendumIndex, ReferendumStatusOf<T, I>)> {
loop {
let (index, _) = track_queue.pop()?;
match Self::ensure_ongoing(index) {
Ok(s) => return Some((index, s)),
Err(_) => {}, }
}
}
fn note_one_fewer_deciding(track: TrackIdOf<T, I>) {
let now = frame_system::Pallet::<T>::block_number();
let next_block = now + One::one();
let call = match T::Preimages::bound(CallOf::<T, I>::from(Call::one_fewer_deciding {
track,
})) {
Ok(c) => c,
Err(_) => {
debug_assert!(false, "Unable to create a bounded call from `one_fewer_deciding`??",);
return
},
};
Self::set_alarm(call, next_block);
}
fn ensure_alarm_at(
status: &mut ReferendumStatusOf<T, I>,
index: ReferendumIndex,
alarm: BlockNumberFor<T>,
) -> bool {
if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) {
Self::ensure_no_alarm(status);
let call =
match T::Preimages::bound(CallOf::<T, I>::from(Call::nudge_referendum { index })) {
Ok(c) => c,
Err(_) => {
debug_assert!(
false,
"Unable to create a bounded call from `nudge_referendum`??",
);
return false
},
};
status.alarm = Self::set_alarm(call, alarm);
true
} else {
false
}
}
fn service_referendum(
now: BlockNumberFor<T>,
index: ReferendumIndex,
mut status: ReferendumStatusOf<T, I>,
) -> (ReferendumInfoOf<T, I>, bool, ServiceBranch) {
let mut dirty = false;
let track = match Self::track(status.track) {
Some(x) => x,
None => return (ReferendumInfo::Ongoing(status), false, ServiceBranch::Fail),
};
let timeout = status.submitted + T::UndecidingTimeout::get();
let mut alarm = BlockNumberFor::<T>::max_value();
let branch;
match &mut status.deciding {
None => {
if status.in_queue {
let ayes = status.tally.ayes(status.track);
let mut queue = TrackQueue::<T, I>::get(status.track);
let maybe_old_pos = queue.iter().position(|(x, _)| *x == index);
let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x);
branch = if maybe_old_pos.is_none() && new_pos > 0 {
let _ = queue.force_insert_keep_right(new_pos, (index, ayes));
ServiceBranch::RequeuedInsertion
} else if let Some(old_pos) = maybe_old_pos {
queue[old_pos].1 = ayes;
queue.slide(old_pos, new_pos);
ServiceBranch::RequeuedSlide
} else {
ServiceBranch::NotQueued
};
TrackQueue::<T, I>::insert(status.track, queue);
} else {
branch = if status.decision_deposit.is_some() {
let prepare_end = status.submitted.saturating_add(track.prepare_period);
if now >= prepare_end {
let (maybe_alarm, branch) =
Self::ready_for_deciding(now, track, index, &mut status);
if let Some(set_alarm) = maybe_alarm {
alarm = alarm.min(set_alarm);
}
dirty = true;
branch
} else {
alarm = alarm.min(prepare_end);
ServiceBranch::Preparing
}
} else {
alarm = timeout;
ServiceBranch::NoDeposit
}
}
if status.deciding.is_none() && now >= timeout && !status.in_queue {
Self::ensure_no_alarm(&mut status);
Self::deposit_event(Event::<T, I>::TimedOut { index, tally: status.tally });
return (
ReferendumInfo::TimedOut(
now,
Some(status.submission_deposit),
status.decision_deposit,
),
true,
ServiceBranch::TimedOut,
)
}
},
Some(deciding) => {
let is_passing = Self::is_passing(
&status.tally,
now.saturating_sub(deciding.since),
track.decision_period,
&track.min_support,
&track.min_approval,
status.track,
);
branch = if is_passing {
match deciding.confirming {
Some(t) if now >= t => {
Self::ensure_no_alarm(&mut status);
Self::note_one_fewer_deciding(status.track);
let (desired, call) = (status.enactment, status.proposal);
Self::schedule_enactment(index, track, desired, status.origin, call);
Self::deposit_event(Event::<T, I>::Confirmed {
index,
tally: status.tally,
});
return (
ReferendumInfo::Approved(
now,
Some(status.submission_deposit),
status.decision_deposit,
),
true,
ServiceBranch::Approved,
)
},
Some(_) => ServiceBranch::ContinueConfirming,
None => {
dirty = true;
deciding.confirming = Some(now.saturating_add(track.confirm_period));
Self::deposit_event(Event::<T, I>::ConfirmStarted { index });
ServiceBranch::BeginConfirming
},
}
} else {
if now >= deciding.since.saturating_add(track.decision_period) {
Self::ensure_no_alarm(&mut status);
Self::note_one_fewer_deciding(status.track);
Self::deposit_event(Event::<T, I>::Rejected { index, tally: status.tally });
return (
ReferendumInfo::Rejected(
now,
Some(status.submission_deposit),
status.decision_deposit,
),
true,
ServiceBranch::Rejected,
)
}
if deciding.confirming.is_some() {
dirty = true;
deciding.confirming = None;
Self::deposit_event(Event::<T, I>::ConfirmAborted { index });
ServiceBranch::EndConfirming
} else {
ServiceBranch::ContinueNotConfirming
}
};
alarm = Self::decision_time(deciding, &status.tally, status.track, track);
},
}
let dirty_alarm = if alarm < BlockNumberFor::<T>::max_value() {
Self::ensure_alarm_at(&mut status, index, alarm)
} else {
Self::ensure_no_alarm(&mut status)
};
(ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch)
}
fn decision_time(
deciding: &DecidingStatusOf<T>,
tally: &T::Tally,
track_id: TrackIdOf<T, I>,
track: &TrackInfoOf<T, I>,
) -> BlockNumberFor<T> {
deciding.confirming.unwrap_or_else(|| {
let approval = tally.approval(track_id);
let support = tally.support(track_id);
let until_approval = track.min_approval.delay(approval);
let until_support = track.min_support.delay(support);
let offset = until_support.max(until_approval);
deciding.since.saturating_add(offset.mul_ceil(track.decision_period))
})
}
fn ensure_no_alarm(status: &mut ReferendumStatusOf<T, I>) -> bool {
if let Some((_, last_alarm)) = status.alarm.take() {
let _ = T::Scheduler::cancel(last_alarm);
true
} else {
false
}
}
fn take_deposit(
who: T::AccountId,
amount: BalanceOf<T, I>,
) -> Result<Deposit<T::AccountId, BalanceOf<T, I>>, DispatchError> {
T::Currency::reserve(&who, amount)?;
Ok(Deposit { who, amount })
}
fn refund_deposit(deposit: Option<Deposit<T::AccountId, BalanceOf<T, I>>>) {
if let Some(Deposit { who, amount }) = deposit {
T::Currency::unreserve(&who, amount);
}
}
fn slash_deposit(deposit: Option<Deposit<T::AccountId, BalanceOf<T, I>>>) {
if let Some(Deposit { who, amount }) = deposit {
T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0);
Self::deposit_event(Event::<T, I>::DepositSlashed { who, amount });
}
}
fn track(id: TrackIdOf<T, I>) -> Option<&'static TrackInfoOf<T, I>> {
let tracks = T::Tracks::tracks();
let index = tracks.binary_search_by_key(&id, |x| x.0).unwrap_or_else(|x| x);
Some(&tracks[index].1)
}
fn is_passing(
tally: &T::Tally,
elapsed: BlockNumberFor<T>,
period: BlockNumberFor<T>,
support_needed: &Curve,
approval_needed: &Curve,
id: TrackIdOf<T, I>,
) -> bool {
let x = Perbill::from_rational(elapsed.min(period), period);
support_needed.passing(x, tally.support(id)) &&
approval_needed.passing(x, tally.approval(id))
}
fn do_clear_metadata(index: ReferendumIndex) {
if let Some(hash) = MetadataOf::<T, I>::take(index) {
Self::deposit_event(Event::<T, I>::MetadataCleared { index, hash });
}
}
#[cfg(any(feature = "try-runtime", test))]
fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
ensure!(
ReferendumCount::<T, I>::get() as usize ==
ReferendumInfoFor::<T, I>::iter_keys().count(),
"Number of referenda in `ReferendumInfoFor` is different than `ReferendumCount`"
);
MetadataOf::<T, I>::iter_keys().try_for_each(|referendum_index| -> DispatchResult {
ensure!(
ReferendumInfoFor::<T, I>::contains_key(referendum_index),
"Referendum indices in `MetadataOf` must also be stored in `ReferendumInfoOf`"
);
Ok(())
})?;
Self::try_state_referenda_info()?;
Self::try_state_tracks()?;
Ok(())
}
#[cfg(any(feature = "try-runtime", test))]
fn try_state_referenda_info() -> Result<(), sp_runtime::TryRuntimeError> {
ReferendumInfoFor::<T, I>::iter().try_for_each(|(_, referendum)| {
match referendum {
ReferendumInfo::Ongoing(status) => {
ensure!(
Self::track(status.track).is_some(),
"No track info for the track of the referendum."
);
if let Some(deciding) = status.deciding {
ensure!(
deciding.since <
deciding.confirming.unwrap_or(BlockNumberFor::<T>::max_value()),
"Deciding status cannot begin before confirming stage."
)
}
},
_ => {},
}
Ok(())
})
}
#[cfg(any(feature = "try-runtime", test))]
fn try_state_tracks() -> Result<(), sp_runtime::TryRuntimeError> {
T::Tracks::tracks().iter().try_for_each(|track| {
TrackQueue::<T, I>::get(track.0).iter().try_for_each(
|(referendum_index, _)| -> Result<(), sp_runtime::TryRuntimeError> {
ensure!(
ReferendumInfoFor::<T, I>::contains_key(referendum_index),
"`ReferendumIndex` inside the `TrackQueue` should be a key in `ReferendumInfoFor`"
);
Ok(())
},
)?;
Ok(())
})
}
}