#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::boxed_local)]
#![allow(clippy::borrowed_box)]
#![allow(clippy::unused_unit)]
use frame_support::{
dispatch::PostDispatchInfo,
pallet_prelude::*,
traits::{
schedule::{DispatchTime, Named as ScheduleNamed, Priority},
EnsureOrigin, Get, IsType, OriginTrait,
},
weights::GetDispatchInfo,
};
use frame_system::pallet_prelude::*;
use sp_runtime::{
traits::{CheckedSub, Dispatchable, Saturating},
DispatchError, DispatchResult, RuntimeDebug,
};
use sp_std::prelude::*;
mod default_weight;
mod mock;
mod tests;
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode)]
pub struct DelayedOrigin<BlockNumber, PalletsOrigin> {
pub delay: BlockNumber,
pub origin: Box<PalletsOrigin>,
}
pub struct EnsureDelayed<Delay, Inner, BlockNumber, PalletsOrigin>(
sp_std::marker::PhantomData<(Delay, Inner, BlockNumber, PalletsOrigin)>,
);
impl<
PalletsOrigin: Into<O>,
O: Into<Result<DelayedOrigin<BlockNumber, PalletsOrigin>, O>> + From<DelayedOrigin<BlockNumber, PalletsOrigin>>,
Delay: Get<BlockNumber>,
Inner: EnsureOrigin<O>,
BlockNumber: PartialOrd,
> EnsureOrigin<O> for EnsureDelayed<Delay, Inner, BlockNumber, PalletsOrigin>
{
type Success = Inner::Success;
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|delayed_origin| {
if delayed_origin.delay >= Delay::get() {
let pallets_origin = *delayed_origin.origin;
Inner::try_origin(pallets_origin.into())
} else {
Err(delayed_origin.into())
}
})
}
#[cfg(feature = "runtime-benchmarks")]
fn successful_origin() -> O {
unimplemented!()
}
}
pub trait AuthorityConfig<Origin, PalletsOrigin, BlockNumber> {
fn check_schedule_dispatch(origin: Origin, priority: Priority) -> DispatchResult;
fn check_fast_track_schedule(
origin: Origin,
initial_origin: &PalletsOrigin,
new_delay: BlockNumber,
) -> DispatchResult;
fn check_delay_schedule(origin: Origin, initial_origin: &PalletsOrigin) -> DispatchResult;
fn check_cancel_schedule(origin: Origin, initial_origin: &PalletsOrigin) -> DispatchResult;
}
pub trait AsOriginId<Origin, PalletsOrigin> {
fn into_origin(self) -> PalletsOrigin;
fn check_dispatch_from(&self, origin: Origin) -> DispatchResult;
}
pub type ScheduleTaskIndex = u32;
pub use module::*;
#[frame_support::pallet]
pub mod module {
use super::*;
pub trait WeightInfo {
fn dispatch_as() -> Weight;
fn schedule_dispatch_without_delay() -> Weight;
fn schedule_dispatch_with_delay() -> Weight;
fn fast_track_scheduled_dispatch() -> Weight;
fn delay_scheduled_dispatch() -> Weight;
fn cancel_scheduled_dispatch() -> Weight;
}
pub type Origin<T> = DelayedOrigin<<T as frame_system::Config>::BlockNumber, <T as Config>::PalletsOrigin>;
pub(crate) type CallOf<T> = <T as Config>::Call;
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
type Origin: From<DelayedOrigin<Self::BlockNumber, <Self as Config>::PalletsOrigin>>
+ IsType<<Self as frame_system::Config>::Origin>
+ OriginTrait<PalletsOrigin = Self::PalletsOrigin>;
type PalletsOrigin: Parameter + Into<<Self as frame_system::Config>::Origin>;
type Call: Parameter
+ Dispatchable<Origin = <Self as frame_system::Config>::Origin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo;
type Scheduler: ScheduleNamed<Self::BlockNumber, <Self as Config>::Call, Self::PalletsOrigin>;
type AsOriginId: Parameter + AsOriginId<<Self as frame_system::Config>::Origin, Self::PalletsOrigin>;
type AuthorityConfig: AuthorityConfig<
<Self as frame_system::Config>::Origin,
Self::PalletsOrigin,
Self::BlockNumber,
>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
Overflow,
FailedToSchedule,
FailedToCancel,
FailedToFastTrack,
FailedToDelay,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
Dispatched(DispatchResult),
Scheduled(T::PalletsOrigin, ScheduleTaskIndex),
FastTracked(T::PalletsOrigin, ScheduleTaskIndex, T::BlockNumber),
Delayed(T::PalletsOrigin, ScheduleTaskIndex, T::BlockNumber),
Cancelled(T::PalletsOrigin, ScheduleTaskIndex),
}
#[pallet::storage]
#[pallet::getter(fn next_task_index)]
pub type NextTaskIndex<T: Config> = StorageValue<_, ScheduleTaskIndex, ValueQuery>;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight({
let info = call.get_dispatch_info();
(T::WeightInfo::dispatch_as().saturating_add(info.weight), info.class)
})]
pub fn dispatch_as(
origin: OriginFor<T>,
as_origin: T::AsOriginId,
call: Box<CallOf<T>>,
) -> DispatchResultWithPostInfo {
as_origin.check_dispatch_from(origin)?;
let e = call.dispatch(as_origin.into_origin().into());
Self::deposit_event(Event::Dispatched(e.map(|_| ()).map_err(|e| e.error)));
Ok(().into())
}
#[pallet::weight(T::WeightInfo::schedule_dispatch_without_delay())]
pub fn schedule_dispatch(
origin: OriginFor<T>,
when: DispatchTime<T::BlockNumber>,
priority: Priority,
with_delayed_origin: bool,
call: Box<CallOf<T>>,
) -> DispatchResultWithPostInfo {
T::AuthorityConfig::check_schedule_dispatch(origin.clone(), priority)?;
let id = NextTaskIndex::<T>::mutate(|id| -> sp_std::result::Result<ScheduleTaskIndex, DispatchError> {
let current_id = *id;
*id = id.checked_add(1).ok_or(Error::<T>::Overflow)?;
Ok(current_id)
})?;
let now = frame_system::Module::<T>::block_number();
let delay = match when {
DispatchTime::At(x) => x.checked_sub(&now).ok_or(Error::<T>::Overflow)?,
DispatchTime::After(x) => x,
};
let schedule_origin = if with_delayed_origin {
let origin: <T as Config>::Origin = From::from(origin);
let origin: <T as Config>::Origin = From::from(DelayedOrigin::<T::BlockNumber, T::PalletsOrigin> {
delay,
origin: Box::new(origin.caller().clone()),
});
origin
} else {
<T as Config>::Origin::from(origin)
};
let pallets_origin = schedule_origin.caller().clone();
T::Scheduler::schedule_named(
Encode::encode(&(&pallets_origin, id)),
when,
None,
priority,
pallets_origin.clone(),
*call,
)
.map_err(|_| Error::<T>::FailedToSchedule)?;
Self::deposit_event(Event::Scheduled(pallets_origin, id));
Ok(().into())
}
#[pallet::weight(T::WeightInfo::fast_track_scheduled_dispatch())]
pub fn fast_track_scheduled_dispatch(
origin: OriginFor<T>,
initial_origin: T::PalletsOrigin,
task_id: ScheduleTaskIndex,
when: DispatchTime<T::BlockNumber>,
) -> DispatchResultWithPostInfo {
let now = frame_system::Module::<T>::block_number();
let new_delay = match when {
DispatchTime::At(x) => x.checked_sub(&now).ok_or(Error::<T>::Overflow)?,
DispatchTime::After(x) => x,
};
let dispatch_at = match when {
DispatchTime::At(x) => x,
DispatchTime::After(x) => now.saturating_add(x),
};
T::AuthorityConfig::check_fast_track_schedule(origin, &initial_origin, new_delay)?;
T::Scheduler::reschedule_named((&initial_origin, task_id).encode(), when)
.map_err(|_| Error::<T>::FailedToFastTrack)?;
Self::deposit_event(Event::FastTracked(initial_origin, task_id, dispatch_at));
Ok(().into())
}
#[pallet::weight(T::WeightInfo::delay_scheduled_dispatch())]
pub fn delay_scheduled_dispatch(
origin: OriginFor<T>,
initial_origin: T::PalletsOrigin,
task_id: ScheduleTaskIndex,
additional_delay: T::BlockNumber,
) -> DispatchResultWithPostInfo {
T::AuthorityConfig::check_delay_schedule(origin, &initial_origin)?;
T::Scheduler::reschedule_named(
(&initial_origin, task_id).encode(),
DispatchTime::After(additional_delay),
)
.map_err(|_| Error::<T>::FailedToDelay)?;
let now = frame_system::Module::<T>::block_number();
let dispatch_at = now.saturating_add(additional_delay);
Self::deposit_event(Event::Delayed(initial_origin, task_id, dispatch_at));
Ok(().into())
}
#[pallet::weight(T::WeightInfo::cancel_scheduled_dispatch())]
pub fn cancel_scheduled_dispatch(
origin: OriginFor<T>,
initial_origin: T::PalletsOrigin,
task_id: ScheduleTaskIndex,
) -> DispatchResultWithPostInfo {
T::AuthorityConfig::check_cancel_schedule(origin, &initial_origin)?;
T::Scheduler::cancel_named((&initial_origin, task_id).encode()).map_err(|_| Error::<T>::FailedToCancel)?;
Self::deposit_event(Event::Cancelled(initial_origin, task_id));
Ok(().into())
}
}
}