From 8e3204496fb18cab6af0fa04e251fae3232b3fa1 Mon Sep 17 00:00:00 2001 From: Stanly Johnson Date: Sun, 9 May 2021 23:30:23 +0530 Subject: [PATCH 01/10] migrate to pallet! --- frame/scheduler/src/lib.rs | 3049 ++++++++++++++++++++---------------- 1 file changed, 1673 insertions(+), 1376 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 5332aedf7f136..69e4c7efa3757 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -16,15 +16,15 @@ // limitations under the License. //! # Scheduler -//! A module for scheduling dispatches. +//! A Pallet for scheduling dispatches. //! //! - [`Config`] //! - [`Call`] -//! - [`Module`] +//! - [`Pallet`] //! //! ## Overview //! -//! This module exposes capabilities for scheduling dispatches to occur at a +//! This Pallet exposes capabilities for scheduling dispatches to occur at a //! specified block number or at a specified period. These scheduled dispatches //! may be named or anonymous and may be canceled. //! @@ -58,47 +58,14 @@ use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow}; use codec::{Encode, Decode, Codec}; use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin, Saturating}}; use frame_support::{ - decl_module, decl_storage, decl_event, decl_error, IterableStorageMap, + IterableStorageMap, dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter}, traits::{Get, schedule::{self, DispatchTime}, OriginTrait, EnsureOrigin, IsType}, weights::{GetDispatchInfo, Weight}, }; use frame_system::{self as system, ensure_signed}; pub use weights::WeightInfo; - -/// Our pallet's configuration trait. All our types and constants go in here. If the -/// pallet is dependent on specific other pallets, then their configuration traits -/// should be added to our implied traits list. -/// -/// `system::Config` should always be included in our implied traits. -pub trait Config: system::Config { - /// The overarching event type. - type Event: From> + Into<::Event>; - - /// The aggregated origin which the dispatch will take. - type Origin: OriginTrait - + From + IsType<::Origin>; - - /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin: From> + Codec + Clone + Eq; - - /// The aggregated call type. - type Call: Parameter + Dispatchable::Origin> + GetDispatchInfo + From>; - - /// The maximum weight that may be scheduled per block for any dispatchables of less priority - /// than `schedule::HARD_DEADLINE`. - type MaximumWeight: Get; - - /// Required origin to schedule or cancel calls. - type ScheduleOrigin: EnsureOrigin<::Origin>; - - /// The maximum number of scheduled calls in the queue for a single block. - /// Not strictly enforced, but used for weight estimation. - type MaxScheduledPerBlock: Get; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; -} +pub use pallet::*; /// Just a simple index for naming period tasks. pub type PeriodicIndex = u32; @@ -108,213 +75,140 @@ pub type TaskAddress = (BlockNumber, u32); #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] struct ScheduledV1 { - maybe_id: Option>, - priority: schedule::Priority, - call: Call, - maybe_periodic: Option>, + maybe_id: Option>, + priority: schedule::Priority, + call: Call, + maybe_periodic: Option>, } /// Information regarding an item to be executed in the future. #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] pub struct ScheduledV2 { - /// The unique identity for this task, if there is one. - maybe_id: Option>, - /// This task's priority. - priority: schedule::Priority, - /// The call to be dispatched. - call: Call, - /// If the call is periodic, then this points to the information concerning that. - maybe_periodic: Option>, - /// The origin to dispatch the call. - origin: PalletsOrigin, - _phantom: PhantomData, + /// The unique identity for this task, if there is one. + maybe_id: Option>, + /// This task's priority. + priority: schedule::Priority, + /// The call to be dispatched. + call: Call, + /// If the call is periodic, then this points to the information concerning that. + maybe_periodic: Option>, + /// The origin to dispatch the call. + origin: PalletsOrigin, + _phantom: PhantomData, } /// The current version of Scheduled struct. -pub type Scheduled = ScheduledV2; +pub type Scheduled = + ScheduledV2; // A value placed in storage that represents the current version of the Scheduler storage. // This value is used by the `on_runtime_upgrade` logic to determine whether we run // storage migration logic. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] enum Releases { - V1, - V2, + V1, + V2, } impl Default for Releases { - fn default() -> Self { - Releases::V1 - } -} - -decl_storage! { - trait Store for Module as Scheduler { - /// Items to be executed, indexed by the block number that they should be executed on. - pub Agenda: map hasher(twox_64_concat) T::BlockNumber - => Vec::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>; - - /// Lookup from identity to the block number and index of the task. - Lookup: map hasher(twox_64_concat) Vec => Option>; - - /// Storage version of the pallet. - /// - /// New networks start with last version. - StorageVersion build(|_| Releases::V2): Releases; - } + fn default() -> Self { + Releases::V1 + } } -decl_event!( - pub enum Event where ::BlockNumber { - /// Scheduled some task. \[when, index\] - Scheduled(BlockNumber, u32), - /// Canceled some task. \[when, index\] - Canceled(BlockNumber, u32), - /// Dispatched some task. \[task, id, result\] - Dispatched(TaskAddress, Option>, DispatchResult), - } -); - -decl_error! { - pub enum Error for Module { - /// Failed to schedule a call - FailedToSchedule, - /// Cannot find the scheduled call. - NotFound, - /// Given target block number is in the past. - TargetBlockNumberInPast, - /// Reschedule failed because it does not change scheduled time. - RescheduleNoChange, - } -} - -decl_module! { - /// Scheduler module declaration. - pub struct Module for enum Call where origin: ::Origin { - type Error = Error; - fn deposit_event() = default; - - /// Anonymously schedule a task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.29 + .126 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda - /// - Will use base weight of 25 which should be good for up to 30 scheduled calls - /// # - #[weight = T::WeightInfo::schedule(T::MaxScheduledPerBlock::get())] - fn schedule(origin, - when: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?; - } - - /// Cancel an anonymously scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.15 + 2.869 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # - #[weight = T::WeightInfo::cancel(T::MaxScheduledPerBlock::get())] - fn cancel(origin, when: T::BlockNumber, index: u32) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_cancel(Some(origin.caller().clone()), (when, index))?; - } - - /// Schedule a named task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 29.6 + .159 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 35 which should be good for more than 30 scheduled calls - /// # - #[weight = T::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())] - fn schedule_named(origin, - id: Vec, - when: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule_named( - id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call - )?; - } - - /// Cancel a named scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 24.91 + 2.907 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # - #[weight = T::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())] - fn cancel_named(origin, id: Vec) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_cancel_named(Some(origin.caller().clone()), id)?; - } - - /// Anonymously schedule a task after a delay. - /// - /// # - /// Same as [`schedule`]. - /// # - #[weight = T::WeightInfo::schedule(T::MaxScheduledPerBlock::get())] - fn schedule_after(origin, - after: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule( - DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call - )?; - } - - /// Schedule a named task after a delay. - /// - /// # - /// Same as [`schedule_named`]. - /// # - #[weight = T::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())] - fn schedule_named_after(origin, - id: Vec, - after: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule_named( - id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call - )?; - } - +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// Our pallet's configuration trait. All our types and constants go in here. If the + /// pallet is dependent on specific other pallets, then their configuration traits + /// should be added to our implied traits list. + /// + /// `system::Config` should always be included in our implied traits. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The aggregated origin which the dispatch will take. + type Origin: OriginTrait + + From + + IsType<::Origin>; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: From> + Codec + Clone + Eq; + + /// The aggregated call type. + type Call: Parameter + + Dispatchable::Origin> + + GetDispatchInfo + + From>; + + /// The maximum weight that may be scheduled per block for any dispatchables of less priority + /// than `schedule::HARD_DEADLINE`. + type MaximumWeight: Get; + + /// Required origin to schedule or cancel calls. + type ScheduleOrigin: EnsureOrigin<::Origin>; + + /// The maximum number of scheduled calls in the queue for a single block. + /// Not strictly enforced, but used for weight estimation. + type MaxScheduledPerBlock: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Items to be executed, indexed by the block number that they should be executed on. + #[pallet::storage] + pub(super) type Agenda = StorageMap< + _, + Twox64Concat, + T::BlockNumber, + Vec::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>, + ValueQuery + >; + + /// Lookup from identity to the block number and index of the task. + #[pallet::storage] + pub(crate) type Lookup = StorageMap<_, Twox64Concat, Vec, TaskAddress>; + + #[pallet::storage] + pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; + + /// Events type. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Scheduled some task. \[when, index\] + Scheduled(T::BlockNumber, u32), + /// Canceled some task. \[when, index\] + Canceled(T::BlockNumber, u32), + /// Dispatched some task. \[task, id, result\] + Dispatched(TaskAddress, Option>, DispatchResult), + } + + #[pallet::error] + pub enum Error { + /// Failed to schedule a call + FailedToSchedule, + /// Cannot find the scheduled call. + NotFound, + /// Given target block number is in the past. + TargetBlockNumberInPast, + /// Reschedule failed because it does not change scheduled time. + RescheduleNoChange, + } + + #[pallet::hooks] + impl Hooks> for Pallet { /// Execute the scheduled calls /// /// # @@ -395,7 +289,7 @@ decl_module! { Lookup::::remove(id); } } - Self::deposit_event(RawEvent::Dispatched( + Self::deposit_event(Event::Dispatched( (now, index), maybe_id, r.map(|_| ()).map_err(|e| e.error) @@ -414,1164 +308,1567 @@ decl_module! { total_weight } } -} - -impl Module { - /// Migrate storage format from V1 to V2. - /// Return true if migration is performed. - pub fn migrate_v1_to_t2() -> bool { - if StorageVersion::get() == Releases::V1 { - StorageVersion::put(Releases::V2); - - Agenda::::translate::< - Vec::Call, T::BlockNumber>>>, _ - >(|_, agenda| Some( - agenda - .into_iter() - .map(|schedule| schedule.map(|schedule| ScheduledV2 { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: system::RawOrigin::Root.into(), - _phantom: Default::default(), - })) - .collect::>() - )); - - true - } else { - false - } - } - - /// Helper to migrate scheduler when the pallet origin type has changed. - pub fn migrate_origin + codec::Decode>() { - Agenda::::translate::< - Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _ - >(|_, agenda| Some( - agenda - .into_iter() - .map(|schedule| schedule.map(|schedule| Scheduled { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: schedule.origin.into(), - _phantom: Default::default(), - })) - .collect::>() - )); - } - - fn resolve_time(when: DispatchTime) -> Result { - let now = frame_system::Pallet::::block_number(); - - let when = match when { - DispatchTime::At(x) => x, - // The current block has already completed it's scheduled tasks, so - // Schedule the task at lest one block after this current block. - DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()) - }; - - if when <= now { - return Err(Error::::TargetBlockNumberInPast.into()) - } - - Ok(when) - } - - fn do_schedule( - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call - ) -> Result, DispatchError> { - let when = Self::resolve_time(when)?; - - // sanitize maybe_periodic - let maybe_periodic = maybe_periodic - .filter(|p| p.1 > 1 && !p.0.is_zero()) - // Remove one from the number of repetitions since we will schedule one now. - .map(|(p, c)| (p, c - 1)); - let s = Some(Scheduled { - maybe_id: None, priority, call, maybe_periodic, origin, _phantom: PhantomData::::default(), - }); - Agenda::::append(when, s); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - Self::deposit_event(RawEvent::Scheduled(when, index)); - - Ok((when, index)) - } - - fn do_cancel( - origin: Option, - (when, index): TaskAddress - ) -> Result<(), DispatchError> { - let scheduled = Agenda::::try_mutate( - when, - |agenda| { - agenda.get_mut(index as usize) - .map_or(Ok(None), |s| -> Result>, DispatchError> { - if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { - return Err(BadOrigin.into()); - } - }; - Ok(s.take()) - }) - }, - )?; - if let Some(s) = scheduled { - if let Some(id) = s.maybe_id { - Lookup::::remove(id); - } - Self::deposit_event(RawEvent::Canceled(when, index)); - Ok(()) - } else { - Err(Error::::NotFound)? - } - } - - fn do_reschedule( - (when, index): TaskAddress, - new_time: DispatchTime, - ) -> Result, DispatchError> { - let new_time = Self::resolve_time(new_time)?; - - if new_time == when { - return Err(Error::::RescheduleNoChange.into()); - } - - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); - Ok(()) - })?; - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(RawEvent::Canceled(when, index)); - Self::deposit_event(RawEvent::Scheduled(new_time, new_index)); - - Ok((new_time, new_index)) - } - - fn do_schedule_named( - id: Vec, - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result, DispatchError> { - // ensure id it is unique - if Lookup::::contains_key(&id) { - return Err(Error::::FailedToSchedule)? - } - - let when = Self::resolve_time(when)?; - - // sanitize maybe_periodic - let maybe_periodic = maybe_periodic - .filter(|p| p.1 > 1 && !p.0.is_zero()) - // Remove one from the number of repetitions since we will schedule one now. - .map(|(p, c)| (p, c - 1)); - - let s = Scheduled { - maybe_id: Some(id.clone()), priority, call, maybe_periodic, origin, _phantom: Default::default() - }; - Agenda::::append(when, Some(s)); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - let address = (when, index); - Lookup::::insert(&id, &address); - Self::deposit_event(RawEvent::Scheduled(when, index)); - - Ok(address) - } - - fn do_cancel_named(origin: Option, id: Vec) -> DispatchResult { - Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { - if let Some((when, index)) = lookup.take() { - let i = index as usize; - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - if let Some(s) = agenda.get_mut(i) { - if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { - return Err(BadOrigin.into()); - } - } - *s = None; - } - Ok(()) - })?; - Self::deposit_event(RawEvent::Canceled(when, index)); - Ok(()) - } else { - Err(Error::::NotFound)? - } - }) - } - - fn do_reschedule_named( - id: Vec, - new_time: DispatchTime, - ) -> Result, DispatchError> { - let new_time = Self::resolve_time(new_time)?; - - Lookup::::try_mutate_exists(id, |lookup| -> Result, DispatchError> { - let (when, index) = lookup.ok_or(Error::::NotFound)?; - - if new_time == when { - return Err(Error::::RescheduleNoChange.into()); - } - - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); - - Ok(()) - })?; - - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(RawEvent::Canceled(when, index)); - Self::deposit_event(RawEvent::Scheduled(new_time, new_index)); - - *lookup = Some((new_time, new_index)); - - Ok((new_time, new_index)) - }) - } + #[pallet::call] + impl Pallet { + /// Anonymously schedule a task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 22.29 + .126 * S µs + /// - DB Weight: + /// - Read: Agenda + /// - Write: Agenda + /// - Will use base weight of 25 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule( + origin: OriginFor, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule( + DispatchTime::At(when), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Cancel an anonymously scheduled task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 22.15 + 2.869 * S µs + /// - DB Weight: + /// - Read: Agenda + /// - Write: Agenda, Lookup + /// - Will use base weight of 100 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] + pub fn cancel( + origin: OriginFor, + when: T::BlockNumber, + index: u32, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_cancel(Some(origin.caller().clone()), (when, index))?; + Ok(()) + } + + /// Schedule a named task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 29.6 + .159 * S µs + /// - DB Weight: + /// - Read: Agenda, Lookup + /// - Write: Agenda, Lookup + /// - Will use base weight of 35 which should be good for more than 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named( + origin: OriginFor, + id: Vec, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::At(when), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Cancel a named scheduled task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 24.91 + 2.907 * S µs + /// - DB Weight: + /// - Read: Agenda, Lookup + /// - Write: Agenda, Lookup + /// - Will use base weight of 100 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] + pub fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_cancel_named(Some(origin.caller().clone()), id)?; + Ok(()) + } + + /// Anonymously schedule a task after a delay. + /// + /// # + /// Same as [`schedule`]. + /// # + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule_after( + origin: OriginFor, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule( + DispatchTime::After(after), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Schedule a named task after a delay. + /// + /// # + /// Same as [`schedule_named`]. + /// # + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named_after( + origin: OriginFor, + id: Vec, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::After(after), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + } } -impl schedule::Anon::Call, T::PalletsOrigin> for Module { - type Address = TaskAddress; - - fn schedule( - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call - ) -> Result { - Self::do_schedule(when, maybe_periodic, priority, origin, call) - } - - fn cancel((when, index): Self::Address) -> Result<(), ()> { - Self::do_cancel(None, (when, index)).map_err(|_| ()) - } - - fn reschedule( - address: Self::Address, - when: DispatchTime, - ) -> Result { - Self::do_reschedule(address, when) - } - - fn next_dispatch_time((when, index): Self::Address) -> Result { - Agenda::::get(when).get(index as usize).ok_or(()).map(|_| when) - } +impl Pallet { + /// Migrate storage format from V1 to V2. + /// Return true if migration is performed. + pub fn migrate_v1_to_t2() -> bool { + if StorageVersion::::get() == Releases::V1 { + StorageVersion::::put(Releases::V2); + + Agenda::::translate::< + Vec::Call, T::BlockNumber>>>, + _, + >(|_, agenda| { + Some( + agenda + .into_iter() + .map(|schedule| { + schedule.map(|schedule| ScheduledV2 { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: system::RawOrigin::Root.into(), + _phantom: Default::default(), + }) + }) + .collect::>(), + ) + }); + + true + } else { + false + } + } + + /// Helper to migrate scheduler when the pallet origin type has changed. + pub fn migrate_origin + codec::Decode>() { + Agenda::::translate::< + Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, + _, + >(|_, agenda| { + Some( + agenda + .into_iter() + .map(|schedule| { + schedule.map(|schedule| Scheduled { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin.into(), + _phantom: Default::default(), + }) + }) + .collect::>(), + ) + }); + } + + fn resolve_time(when: DispatchTime) -> Result { + let now = frame_system::Pallet::::block_number(); + + let when = match when { + DispatchTime::At(x) => x, + // The current block has already completed it's scheduled tasks, so + // Schedule the task at lest one block after this current block. + DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()), + }; + + if when <= now { + return Err(Error::::TargetBlockNumberInPast.into()); + } + + Ok(when) + } + + fn do_schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result, DispatchError> { + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + let s = Some(Scheduled { + maybe_id: None, + priority, + call, + maybe_periodic, + origin, + _phantom: PhantomData::::default(), + }); + Agenda::::append(when, s); + let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; + if index > T::MaxScheduledPerBlock::get() { + log::warn!( + target: "runtime::scheduler", + "Warning: There are more items queued in the Scheduler than \ + expected from the runtime configuration. An update might be needed.", + ); + } + Self::deposit_event(Event::Scheduled(when, index)); + + Ok((when, index)) + } + + fn do_cancel( + origin: Option, + (when, index): TaskAddress, + ) -> Result<(), DispatchError> { + let scheduled = Agenda::::try_mutate(when, |agenda| { + agenda.get_mut(index as usize).map_or( + Ok(None), + |s| -> Result>, DispatchError> { + if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { + if *o != s.origin { + return Err(BadOrigin.into()); + } + }; + Ok(s.take()) + }, + ) + })?; + if let Some(s) = scheduled { + if let Some(id) = s.maybe_id { + Lookup::::remove(id); + } + Self::deposit_event(Event::Canceled(when, index)); + Ok(()) + } else { + Err(Error::::NotFound)? + } + } + + fn do_reschedule( + (when, index): TaskAddress, + new_time: DispatchTime, + ) -> Result, DispatchError> { + let new_time = Self::resolve_time(new_time)?; + + if new_time == when { + return Err(Error::::RescheduleNoChange.into()); + } + + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; + let task = task.take().ok_or(Error::::NotFound)?; + Agenda::::append(new_time, Some(task)); + Ok(()) + })?; + + let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; + Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Scheduled(new_time, new_index)); + + Ok((new_time, new_index)) + } + + fn do_schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result, DispatchError> { + // ensure id it is unique + if Lookup::::contains_key(&id) { + return Err(Error::::FailedToSchedule)?; + } + + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + + let s = Scheduled { + maybe_id: Some(id.clone()), + priority, + call, + maybe_periodic, + origin, + _phantom: Default::default(), + }; + Agenda::::append(when, Some(s)); + let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; + if index > T::MaxScheduledPerBlock::get() { + log::warn!( + target: "runtime::scheduler", + "Warning: There are more items queued in the Scheduler than \ + expected from the runtime configuration. An update might be needed.", + ); + } + let address = (when, index); + Lookup::::insert(&id, &address); + Self::deposit_event(Event::Scheduled(when, index)); + + Ok(address) + } + + fn do_cancel_named(origin: Option, id: Vec) -> DispatchResult { + Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { + if let Some((when, index)) = lookup.take() { + let i = index as usize; + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + if let Some(s) = agenda.get_mut(i) { + if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { + if *o != s.origin { + return Err(BadOrigin.into()); + } + } + *s = None; + } + Ok(()) + })?; + Self::deposit_event(Event::Canceled(when, index)); + Ok(()) + } else { + Err(Error::::NotFound)? + } + }) + } + + fn do_reschedule_named( + id: Vec, + new_time: DispatchTime, + ) -> Result, DispatchError> { + let new_time = Self::resolve_time(new_time)?; + + Lookup::::try_mutate_exists( + id, + |lookup| -> Result, DispatchError> { + let (when, index) = lookup.ok_or(Error::::NotFound)?; + + if new_time == when { + return Err(Error::::RescheduleNoChange.into()); + } + + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; + let task = task.take().ok_or(Error::::NotFound)?; + Agenda::::append(new_time, Some(task)); + + Ok(()) + })?; + + let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; + Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Scheduled(new_time, new_index)); + + *lookup = Some((new_time, new_index)); + + Ok((new_time, new_index)) + }, + ) + } } -impl schedule::Named::Call, T::PalletsOrigin> for Module { - type Address = TaskAddress; - - fn schedule_named( - id: Vec, - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result { - Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ()) - } - - fn cancel_named(id: Vec) -> Result<(), ()> { - Self::do_cancel_named(None, id).map_err(|_| ()) - } - - fn reschedule_named( - id: Vec, - when: DispatchTime, - ) -> Result { - Self::do_reschedule_named(id, when) - } +impl schedule::Anon::Call, T::PalletsOrigin> + for Pallet +{ + type Address = TaskAddress; + + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result { + Self::do_schedule(when, maybe_periodic, priority, origin, call) + } + + fn cancel((when, index): Self::Address) -> Result<(), ()> { + Self::do_cancel(None, (when, index)).map_err(|_| ()) + } + + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result { + Self::do_reschedule(address, when) + } + + fn next_dispatch_time((when, index): Self::Address) -> Result { + Agenda::::get(when) + .get(index as usize) + .ok_or(()) + .map(|_| when) + } +} - fn next_dispatch_time(id: Vec) -> Result { - Lookup::::get(id).and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)).ok_or(()) - } +impl schedule::Named::Call, T::PalletsOrigin> + for Pallet +{ + type Address = TaskAddress; + + fn schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result { + Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ()) + } + + fn cancel_named(id: Vec) -> Result<(), ()> { + Self::do_cancel_named(None, id).map_err(|_| ()) + } + + fn reschedule_named( + id: Vec, + when: DispatchTime, + ) -> Result { + Self::do_reschedule_named(id, when) + } + + fn next_dispatch_time(id: Vec) -> Result { + Lookup::::get(id) + .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) + .ok_or(()) + } } #[cfg(test)] mod tests { - use super::*; - - use frame_support::{ - parameter_types, assert_ok, ord_parameter_types, - assert_noop, assert_err, Hashable, - traits::{OnInitialize, OnFinalize, Filter}, - weights::constants::RocksDbWeight, - }; - use sp_core::H256; - use sp_runtime::{ - Perbill, - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - }; - use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; - use substrate_test_utils::assert_eq_uvec; - use crate as scheduler; - - mod logger { - use super::*; - use std::cell::RefCell; - - thread_local! { - static LOG: RefCell> = RefCell::new(Vec::new()); - } - pub fn log() -> Vec<(OriginCaller, u32)> { - LOG.with(|log| log.borrow().clone()) - } - pub trait Config: system::Config { - type Event: From + Into<::Event>; - } - decl_event! { - pub enum Event { - Logged(u32, Weight), - } - } - decl_module! { - pub struct Module for enum Call - where - origin: ::Origin, - ::Origin: OriginTrait - { - fn deposit_event() = default; - - #[weight = *weight] - fn log(origin, i: u32, weight: Weight) { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }) - } - - #[weight = *weight] - fn log_without_filter(origin, i: u32, weight: Weight) { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }) - } - } - } - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Logger: logger::{Pallet, Call, Event}, - Scheduler: scheduler::{Pallet, Call, Storage, Event}, - } - ); - - // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. - pub struct BaseFilter; - impl Filter for BaseFilter { - fn filter(call: &Call) -> bool { - !matches!(call, Call::Logger(logger::Call::log(_, _))) - } - } - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2_000_000_000_000); - } - impl system::Config for Test { - type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - impl logger::Config for Test { - type Event = Event; - } - parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 10; - } - ord_parameter_types! { - pub const One: u64 = 1; - } - - impl Config for Test { - type Event = Event; - type Origin = Origin; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - } - - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::default().build_storage::().unwrap(); - t.into() - } - - fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } - } - - fn root() -> OriginCaller { - system::RawOrigin::Root.into() - } - - #[test] - fn basic_scheduling_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call)); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 - assert_ok!(Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call)); - run_to_block(5); - assert!(logger::log().is_empty()); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_zero_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - assert_ok!(Scheduler::do_schedule(DispatchTime::After(0), None, 127, root(), call)); - // Will trigger on the next block. - run_to_block(3); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000)) - )); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - }); - } - - #[test] - fn reschedule_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - assert_eq!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), (4, 0)); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); - - assert_noop!(Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), Error::::RescheduleNoChange); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - assert_eq!(Scheduler::do_schedule_named( - 1u32.encode(), DispatchTime::At(4), None, 127, root(), call - ).unwrap(), (4, 0)); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0)); - - assert_noop!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), Error::::RescheduleNoChange); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_perodic_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(logger::Call::log(42, 1000)); - assert!(!::BaseCallFilter::filter(&call)); - assert_eq!(Scheduler::do_schedule_named( - 1u32.encode(), DispatchTime::At(4), Some((3, 3)), 127, root(), call - ).unwrap(), (4, 0)); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), (5, 0)); - assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0)); - - run_to_block(5); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), (10, 0)); - - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(10); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - - run_to_block(13); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - }); - } - - #[test] - fn cancel_named_scheduling_works_with_normal_cancel() { - new_test_ext().execute_with(|| { - // at #4. - Scheduler::do_schedule_named( - 1u32.encode(), DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) - ).unwrap(); - let i = Scheduler::do_schedule( - DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(42, 1000)) - ).unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn cancel_named_periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(logger::Call::log(42, 1000)) - ).unwrap(); - // same id results in error. - assert!(Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(logger::Call::log(69, 1000)) - ).is_err()); - // different id is ok. - Scheduler::do_schedule_named( - 2u32.encode(), DispatchTime::At(8), None, 127, root(), Call::Logger(logger::Call::log(69, 1000)) - ).unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_weight_limits() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // 69 and 42 do not fit together - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_hard_deadlines_more() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // With base weights, 69 and 42 should not fit together, but do because of hard deadlines - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_priority_ordering() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 1, - root(), - Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) - )); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); - }); - } - - #[test] - fn scheduler_respects_priority_ordering_with_soft_deadlines() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 255, - root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 126, - root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2)) - )); - - // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - // 69 and 42 fit together - run_to_block(5); - assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); - }); - } - - #[test] - fn on_initialize_weight_is_correct() { - new_test_ext().execute_with(|| { - let base_weight: Weight = ::DbWeight::get().reads_writes(1, 2); - let base_multiplier = 0; - let named_multiplier = ::DbWeight::get().writes(1); - let periodic_multiplier = ::DbWeight::get().reads_writes(1, 1); - - // Named - assert_ok!( - Scheduler::do_schedule_named( - 1u32.encode(), DispatchTime::At(1), None, 255, root(), - Call::Logger(logger::Call::log(3, MaximumSchedulerWeight::get() / 3)) - ) - ); - // Anon Periodic - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - Some((1000, 3)), - 128, - root(), - Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3)) - )); - // Anon - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - None, - 127, - root(), - Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // Named Periodic - assert_ok!(Scheduler::do_schedule_named( - 2u32.encode(), DispatchTime::At(1), Some((1000, 3)), 126, root(), - Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))) - ); - - // Will include the named periodic only - let actual_weight = Scheduler::on_initialize(1); - let call_weight = MaximumSchedulerWeight::get() / 2; - assert_eq!( - actual_weight, call_weight + base_weight + base_multiplier + named_multiplier + periodic_multiplier - ); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - - // Will include anon and anon periodic - let actual_weight = Scheduler::on_initialize(2); - let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3; - assert_eq!(actual_weight, call_weight + base_weight + base_multiplier * 2 + periodic_multiplier); - assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); - - // Will include named only - let actual_weight = Scheduler::on_initialize(3); - let call_weight = MaximumSchedulerWeight::get() / 3; - assert_eq!(actual_weight, call_weight + base_weight + base_multiplier + named_multiplier); - assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)]); - - // Will contain none - let actual_weight = Scheduler::on_initialize(4); - assert_eq!(actual_weight, 0); - }); - } - - #[test] - fn root_calls_works() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(logger::Call::log(69, 1000))); - let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000))); - assert_ok!(Scheduler::schedule_named(Origin::root(), 1u32.encode(), 4, None, 127, call)); - assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2)); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode())); - assert_ok!(Scheduler::cancel(Origin::root(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn fails_to_schedule_task_in_the_past() { - new_test_ext().execute_with(|| { - run_to_block(3); - - let call = Box::new(Call::Logger(logger::Call::log(69, 1000))); - let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000))); - - assert_err!( - Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call), - Error::::TargetBlockNumberInPast, - ); - - assert_err!( - Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()), - Error::::TargetBlockNumberInPast, - ); - - assert_err!( - Scheduler::schedule(Origin::root(), 3, None, 127, call2), - Error::::TargetBlockNumberInPast, - ); - }); - } - - #[test] - fn should_use_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(logger::Call::log(69, 1000))); - let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000))); - assert_ok!( - Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call) - ); - assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2)); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), 1u32.encode())); - assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn should_check_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(logger::Call::log(69, 1000))); - let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000))); - assert_noop!( - Scheduler::schedule_named(system::RawOrigin::Signed(2).into(), 1u32.encode(), 4, None, 127, call), - BadOrigin - ); - assert_noop!(Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), BadOrigin); - }); - } - - #[test] - fn should_check_orign_for_cancel() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(logger::Call::log_without_filter(69, 1000))); - let call2 = Box::new(Call::Logger(logger::Call::log_without_filter(42, 1000))); - assert_ok!( - Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call) - ); - assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2)); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_noop!(Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), BadOrigin); - assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); - assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), BadOrigin); - assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); - run_to_block(5); - assert_eq!( - logger::log(), - vec![(system::RawOrigin::Signed(1).into(), 69u32), (system::RawOrigin::Signed(1).into(), 42u32)] - ); - }); - } - - #[test] - fn migration_to_v2_works() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old = vec![ - Some(ScheduledV1 { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - }), - None, - Some(ScheduledV1 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - }), - ]; - frame_support::migration::put_storage_value( - b"Scheduler", - b"Agenda", - &k, - old, - ); - } - - assert_eq!(StorageVersion::get(), Releases::V1); - - assert!(Scheduler::migrate_v1_to_t2()); - - assert_eq_uvec!(Agenda::::iter().collect::>(), vec![ - ( - 0, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 10, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ]), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ) - ]); - - assert_eq!(StorageVersion::get(), Releases::V2); - }); - } - - #[test] - fn test_migrate_origin() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old: Vec>> = vec![ - Some(Scheduled { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(logger::Call::log(96, 100)), - origin: 3u32, - maybe_periodic: None, - _phantom: Default::default(), - }), - None, - Some(Scheduled { - maybe_id: Some(b"test".to_vec()), - priority: 123, - origin: 2u32, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - _phantom: Default::default(), - }), - ]; - frame_support::migration::put_storage_value( - b"Scheduler", - b"Agenda", - &k, - old, - ); - } - - impl Into for u32 { - fn into(self) -> OriginCaller { - match self { - 3u32 => system::RawOrigin::Root.into(), - 2u32 => system::RawOrigin::None.into(), - _ => unreachable!("test make no use of it"), - } - } - } - - Scheduler::migrate_origin::(); - - assert_eq_uvec!(Agenda::::iter().collect::>(), vec![ - ( - 0, - vec![ - Some(ScheduledV2::<_, _, OriginCaller, u64> { - maybe_id: None, - priority: 10, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ]), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(logger::Call::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(logger::Call::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ) - ]); - }); - } + use super::*; + + use crate as scheduler; + use frame_support::{ + assert_err, assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{Filter, OnFinalize, OnInitialize}, + weights::constants::RocksDbWeight, + Hashable, + }; + use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; + use sp_core::H256; + use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, + }; + use substrate_test_utils::assert_eq_uvec; + + // Logger module to track execution. + #[frame_support::pallet] + pub mod logger { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use std::cell::RefCell; + + thread_local! { + static LOG: RefCell> = RefCell::new(Vec::new()); + } + pub fn log() -> Vec<(OriginCaller, u32)> { + LOG.with(|log| log.borrow().clone()) + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Logged(u32, Weight), + } + + #[pallet::call] + impl Pallet { + + #[pallet::weight(*weight)] + fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + LOG.with(|log| { + log.borrow_mut().push((origin.caller().clone(), i)); + }); + Ok(()) + } + + #[pallet::weight(*weight)] + fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + LOG.with(|log| { + log.borrow_mut().push((origin.caller().clone(), i)); + }); + Ok(()) + } + } + } + + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Logger: logger::{Pallet, Call, Event}, + Scheduler: scheduler::{Pallet, Call, Storage, Event}, + } + ); + + // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. + pub struct BaseFilter; + impl Filter for BaseFilter { + fn filter(call: &Call) -> bool { + !matches!(call, Call::Logger(LoggerCall::log(_, _))) + } + } + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(2_000_000_000_000); + } + impl system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + } + impl logger::Config for Test { + type Event = Event; + } + parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 10; + } + ord_parameter_types! { + pub const One: u64 = 1; + } + + impl Config for Test { + type Event = Event; + type Origin = Origin; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = (); + } + + pub type LoggerCall = logger::Call; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + t.into() + } + + fn run_to_block(n: u64) { + while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + } + } + + fn root() -> OriginCaller { + system::RawOrigin::Root.into() + } + + #[test] + fn basic_scheduling_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + call + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn schedule_after_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(3), + None, + 127, + root(), + call + )); + run_to_block(5); + assert!(logger::log().is_empty()); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn schedule_after_zero_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(0), + None, + 127, + root(), + call + )); + // Will trigger on the next block. + run_to_block(3); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)) + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(7); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(10); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + run_to_block(100); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn reschedule_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + assert_noop!( + Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn reschedule_named_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + call + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + assert_noop!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn reschedule_named_perodic_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + call + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), + (5, 0) + ); + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + run_to_block(5); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), + (10, 0) + ); + + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + + run_to_block(13); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + + run_to_block(100); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn cancel_named_scheduling_works_with_normal_cancel() { + new_test_ext().execute_with(|| { + // at #4. + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)), + ) + .unwrap(); + let i = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); + assert_ok!(Scheduler::do_cancel(None, i)); + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn cancel_named_periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)), + ) + .unwrap(); + // same id results in error. + assert!(Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)) + ) + .is_err()); + // different id is ok. + Scheduler::do_schedule_named( + 2u32.encode(), + DispatchTime::At(8), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_weight_limits() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // 69 and 42 do not fit together + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_hard_deadlines_more() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // With base weights, 69 and 42 should not fit together, but do because of hard deadlines + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 1, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 255, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 126, + root(), + Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) + )); + + // 2600 does not fit with 69 or 42, but has higher priority, so will go through + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + // 69 and 42 fit together + run_to_block(5); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn on_initialize_weight_is_correct() { + new_test_ext().execute_with(|| { + let base_weight: Weight = + ::DbWeight::get().reads_writes(1, 2); + let base_multiplier = 0; + let named_multiplier = ::DbWeight::get().writes(1); + let periodic_multiplier = + ::DbWeight::get().reads_writes(1, 1); + + // Named + assert_ok!(Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(1), + None, + 255, + root(), + Call::Logger(LoggerCall::log(3, MaximumSchedulerWeight::get() / 3)) + )); + // Anon Periodic + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(1), + Some((1000, 3)), + 128, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) + )); + // Anon + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(1), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // Named Periodic + assert_ok!(Scheduler::do_schedule_named( + 2u32.encode(), + DispatchTime::At(1), + Some((1000, 3)), + 126, + root(), + Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) + )); + + // Will include the named periodic only + let actual_weight = Scheduler::on_initialize(1); + let call_weight = MaximumSchedulerWeight::get() / 2; + assert_eq!( + actual_weight, + call_weight + + base_weight + + base_multiplier + + named_multiplier + + periodic_multiplier + ); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + + // Will include anon and anon periodic + let actual_weight = Scheduler::on_initialize(2); + let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3; + assert_eq!( + actual_weight, + call_weight + base_weight + base_multiplier * 2 + periodic_multiplier + ); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + + // Will include named only + let actual_weight = Scheduler::on_initialize(3); + let call_weight = MaximumSchedulerWeight::get() / 3; + assert_eq!( + actual_weight, + call_weight + base_weight + base_multiplier + named_multiplier + ); + assert_eq!( + logger::log(), + vec![ + (root(), 2600u32), + (root(), 69u32), + (root(), 42u32), + (root(), 3u32) + ] + ); + + // Will contain none + let actual_weight = Scheduler::on_initialize(4); + assert_eq!(actual_weight, 0); + }); + } + + #[test] + fn root_calls_works() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_ok!(Scheduler::schedule_named( + Origin::root(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2)); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode())); + assert_ok!(Scheduler::cancel(Origin::root(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn fails_to_schedule_task_in_the_past() { + new_test_ext().execute_with(|| { + run_to_block(3); + + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + + assert_err!( + Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call), + Error::::TargetBlockNumberInPast, + ); + + assert_err!( + Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()), + Error::::TargetBlockNumberInPast, + ); + + assert_err!( + Scheduler::schedule(Origin::root(), 3, None, 127, call2), + Error::::TargetBlockNumberInPast, + ); + }); + } + + #[test] + fn should_use_orign() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + 127, + call2 + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode() + )); + assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn should_check_orign() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_noop!( + Scheduler::schedule_named( + system::RawOrigin::Signed(2).into(), + 1u32.encode(), + 4, + None, + 127, + call + ), + BadOrigin + ); + assert_noop!( + Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), + BadOrigin + ); + }); + } + + #[test] + fn should_check_orign_for_cancel() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log_without_filter(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log_without_filter(42, 1000))); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + 127, + call2 + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), + BadOrigin + ); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), + BadOrigin + ); + run_to_block(5); + assert_eq!( + logger::log(), + vec![ + (system::RawOrigin::Signed(1).into(), 69u32), + (system::RawOrigin::Signed(1).into(), 42u32) + ] + ); + }); + } + + #[test] + fn migration_to_v2_works() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old = vec![ + Some(ScheduledV1 { + maybe_id: None, + priority: i as u8 + 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + }), + None, + Some(ScheduledV1 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + assert_eq!(StorageVersion::::get(), Releases::V1); + + assert!(Scheduler::migrate_v1_to_t2()); + + assert_eq_uvec!( + Agenda::::iter().collect::>(), + vec![ + ( + 0, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 1, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 11, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 2, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 12, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ) + ] + ); + + assert_eq!(StorageVersion::::get(), Releases::V2); + }); + } + + #[test] + fn test_migrate_origin() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old: Vec>> = vec![ + Some(Scheduled { + maybe_id: None, + priority: i as u8 + 10, + call: Call::Logger(LoggerCall::log(96, 100)), + origin: 3u32, + maybe_periodic: None, + _phantom: Default::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(b"test".to_vec()), + priority: 123, + origin: 2u32, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + _phantom: Default::default(), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + impl Into for u32 { + fn into(self) -> OriginCaller { + match self { + 3u32 => system::RawOrigin::Root.into(), + 2u32 => system::RawOrigin::None.into(), + _ => unreachable!("test make no use of it"), + } + } + } + + Scheduler::migrate_origin::(); + + assert_eq_uvec!( + Agenda::::iter().collect::>(), + vec![ + ( + 0, + vec![ + Some(ScheduledV2::<_, _, OriginCaller, u64> { + maybe_id: None, + priority: 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 1, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 11, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 2, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 12, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ) + ] + ); + }); + } } From 2996b02421c32fc5ba91353a5a58abdc98fe64ad Mon Sep 17 00:00:00 2001 From: Stanly Johnson Date: Mon, 10 May 2021 00:25:59 +0530 Subject: [PATCH 02/10] fixes --- frame/scheduler/src/lib.rs | 3339 ++++++++++++++++++------------------ 1 file changed, 1669 insertions(+), 1670 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 69e4c7efa3757..a9345dc6ab17c 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -58,7 +58,6 @@ use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow}; use codec::{Encode, Decode, Codec}; use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin, Saturating}}; use frame_support::{ - IterableStorageMap, dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter}, traits::{Get, schedule::{self, DispatchTime}, OriginTrait, EnsureOrigin, IsType}, weights::{GetDispatchInfo, Weight}, @@ -75,137 +74,138 @@ pub type TaskAddress = (BlockNumber, u32); #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] struct ScheduledV1 { - maybe_id: Option>, - priority: schedule::Priority, - call: Call, - maybe_periodic: Option>, + maybe_id: Option>, + priority: schedule::Priority, + call: Call, + maybe_periodic: Option>, } /// Information regarding an item to be executed in the future. #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] pub struct ScheduledV2 { - /// The unique identity for this task, if there is one. - maybe_id: Option>, - /// This task's priority. - priority: schedule::Priority, - /// The call to be dispatched. - call: Call, - /// If the call is periodic, then this points to the information concerning that. - maybe_periodic: Option>, - /// The origin to dispatch the call. - origin: PalletsOrigin, - _phantom: PhantomData, + /// The unique identity for this task, if there is one. + maybe_id: Option>, + /// This task's priority. + priority: schedule::Priority, + /// The call to be dispatched. + call: Call, + /// If the call is periodic, then this points to the information concerning that. + maybe_periodic: Option>, + /// The origin to dispatch the call. + origin: PalletsOrigin, + _phantom: PhantomData, } /// The current version of Scheduled struct. pub type Scheduled = - ScheduledV2; + ScheduledV2; // A value placed in storage that represents the current version of the Scheduler storage. // This value is used by the `on_runtime_upgrade` logic to determine whether we run // storage migration logic. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] enum Releases { - V1, - V2, + V1, + V2, } impl Default for Releases { - fn default() -> Self { - Releases::V1 - } + fn default() -> Self { + Releases::V1 + } } #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - /// Our pallet's configuration trait. All our types and constants go in here. If the - /// pallet is dependent on specific other pallets, then their configuration traits - /// should be added to our implied traits list. - /// - /// `system::Config` should always be included in our implied traits. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type Event: From> + IsType<::Event>; - - /// The aggregated origin which the dispatch will take. - type Origin: OriginTrait - + From - + IsType<::Origin>; - - /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin: From> + Codec + Clone + Eq; - - /// The aggregated call type. - type Call: Parameter - + Dispatchable::Origin> - + GetDispatchInfo - + From>; - - /// The maximum weight that may be scheduled per block for any dispatchables of less priority - /// than `schedule::HARD_DEADLINE`. - type MaximumWeight: Get; - - /// Required origin to schedule or cancel calls. - type ScheduleOrigin: EnsureOrigin<::Origin>; - - /// The maximum number of scheduled calls in the queue for a single block. - /// Not strictly enforced, but used for weight estimation. - type MaxScheduledPerBlock: Get; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - } - - /// Items to be executed, indexed by the block number that they should be executed on. - #[pallet::storage] - pub(super) type Agenda = StorageMap< - _, - Twox64Concat, - T::BlockNumber, - Vec::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>, - ValueQuery - >; - - /// Lookup from identity to the block number and index of the task. - #[pallet::storage] - pub(crate) type Lookup = StorageMap<_, Twox64Concat, Vec, TaskAddress>; - - #[pallet::storage] - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - - /// Events type. - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Scheduled some task. \[when, index\] - Scheduled(T::BlockNumber, u32), - /// Canceled some task. \[when, index\] - Canceled(T::BlockNumber, u32), - /// Dispatched some task. \[task, id, result\] - Dispatched(TaskAddress, Option>, DispatchResult), - } - - #[pallet::error] - pub enum Error { - /// Failed to schedule a call - FailedToSchedule, - /// Cannot find the scheduled call. - NotFound, - /// Given target block number is in the past. - TargetBlockNumberInPast, - /// Reschedule failed because it does not change scheduled time. - RescheduleNoChange, - } + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// Our pallet's configuration trait. All our types and constants go in here. If the + /// pallet is dependent on specific other pallets, then their configuration traits + /// should be added to our implied traits list. + /// + /// `system::Config` should always be included in our implied traits. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The aggregated origin which the dispatch will take. + type Origin: OriginTrait + + From + + IsType<::Origin>; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: From> + Codec + Clone + Eq; + + /// The aggregated call type. + type Call: Parameter + + Dispatchable::Origin> + + GetDispatchInfo + + From>; + + /// The maximum weight that may be scheduled per block for any dispatchables of less priority + /// than `schedule::HARD_DEADLINE`. + type MaximumWeight: Get; + + /// Required origin to schedule or cancel calls. + type ScheduleOrigin: EnsureOrigin<::Origin>; + + /// The maximum number of scheduled calls in the queue for a single block. + /// Not strictly enforced, but used for weight estimation. + type MaxScheduledPerBlock: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Items to be executed, indexed by the block number that they should be executed on. + #[pallet::storage] + pub(super) type Agenda = StorageMap< + _, + Twox64Concat, + T::BlockNumber, + Vec::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>, + ValueQuery, + >; + + /// Lookup from identity to the block number and index of the task. + #[pallet::storage] + pub(crate) type Lookup = + StorageMap<_, Twox64Concat, Vec, TaskAddress>; + + #[pallet::storage] + pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; + + /// Events type. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Scheduled some task. \[when, index\] + Scheduled(T::BlockNumber, u32), + /// Canceled some task. \[when, index\] + Canceled(T::BlockNumber, u32), + /// Dispatched some task. \[task, id, result\] + Dispatched(TaskAddress, Option>, DispatchResult), + } + + #[pallet::error] + pub enum Error { + /// Failed to schedule a call + FailedToSchedule, + /// Cannot find the scheduled call. + NotFound, + /// Given target block number is in the past. + TargetBlockNumberInPast, + /// Reschedule failed because it does not change scheduled time. + RescheduleNoChange, + } #[pallet::hooks] impl Hooks> for Pallet { @@ -222,7 +222,8 @@ pub mod pallet { /// # fn on_initialize(now: T::BlockNumber) -> Weight { let limit = T::MaximumWeight::get(); - let mut queued = Agenda::::take(now).into_iter() + let mut queued = Agenda::::take(now) + .into_iter() .enumerate() .filter_map(|(index, s)| s.map(|inner| (index as u32, inner))) .collect::>(); @@ -236,29 +237,32 @@ pub mod pallet { queued.sort_by_key(|(_, s)| s.priority); let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next) let mut total_weight: Weight = 0; - queued.into_iter() + queued + .into_iter() .enumerate() .scan(base_weight, |cumulative_weight, (order, (index, s))| { - *cumulative_weight = cumulative_weight - .saturating_add(s.call.get_dispatch_info().weight); + *cumulative_weight = + cumulative_weight.saturating_add(s.call.get_dispatch_info().weight); - let origin = <::Origin as From>::from( - s.origin.clone() - ).into(); + let origin = + <::Origin as From>::from(s.origin.clone()) + .into(); if ensure_signed(origin).is_ok() { - // AccountData for inner call origin accountdata. - *cumulative_weight = cumulative_weight - .saturating_add(T::DbWeight::get().reads_writes(1, 1)); + // AccountData for inner call origin accountdata. + *cumulative_weight = + cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); } if s.maybe_id.is_some() { // Remove/Modify Lookup - *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1)); + *cumulative_weight = + cumulative_weight.saturating_add(T::DbWeight::get().writes(1)); } if s.maybe_periodic.is_some() { // Read/Write Agenda for future block - *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + *cumulative_weight = + cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); } Some((order, index, *cumulative_weight, s)) @@ -268,7 +272,10 @@ pub mod pallet { // - It's priority is `HARD_DEADLINE` // - It does not push the weight past the limit. // - It is the first item in the schedule - if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 { + if s.priority <= schedule::HARD_DEADLINE + || cumulative_weight <= limit + || order == 0 + { let r = s.call.clone().dispatch(s.origin.clone().into()); let maybe_id = s.maybe_id.clone(); if let &Some((period, count)) = &s.maybe_periodic { @@ -292,7 +299,7 @@ pub mod pallet { Self::deposit_event(Event::Dispatched( (now, index), maybe_id, - r.map(|_| ()).map_err(|e| e.error) + r.map(|_| ()).map_err(|e| e.error), )); total_weight = cumulative_weight; None @@ -309,1566 +316,1558 @@ pub mod pallet { } } - #[pallet::call] - impl Pallet { - /// Anonymously schedule a task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.29 + .126 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda - /// - Will use base weight of 25 which should be good for up to 30 scheduled calls - /// # - #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] - pub fn schedule( - origin: OriginFor, - when: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule( - DispatchTime::At(when), - maybe_periodic, - priority, - origin.caller().clone(), - *call, - )?; - Ok(()) - } - - /// Cancel an anonymously scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 22.15 + 2.869 * S µs - /// - DB Weight: - /// - Read: Agenda - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # - #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] - pub fn cancel( - origin: OriginFor, - when: T::BlockNumber, - index: u32, - ) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_cancel(Some(origin.caller().clone()), (when, index))?; - Ok(()) - } - - /// Schedule a named task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 29.6 + .159 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 35 which should be good for more than 30 scheduled calls - /// # - #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] - pub fn schedule_named( - origin: OriginFor, - id: Vec, - when: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule_named( - id, - DispatchTime::At(when), - maybe_periodic, - priority, - origin.caller().clone(), - *call, - )?; - Ok(()) - } - - /// Cancel a named scheduled task. - /// - /// # - /// - S = Number of already scheduled calls - /// - Base Weight: 24.91 + 2.907 * S µs - /// - DB Weight: - /// - Read: Agenda, Lookup - /// - Write: Agenda, Lookup - /// - Will use base weight of 100 which should be good for up to 30 scheduled calls - /// # - #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] - pub fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_cancel_named(Some(origin.caller().clone()), id)?; - Ok(()) - } - - /// Anonymously schedule a task after a delay. - /// - /// # - /// Same as [`schedule`]. - /// # - #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] - pub fn schedule_after( - origin: OriginFor, - after: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule( - DispatchTime::After(after), - maybe_periodic, - priority, - origin.caller().clone(), - *call, - )?; - Ok(()) - } - - /// Schedule a named task after a delay. - /// - /// # - /// Same as [`schedule_named`]. - /// # - #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] - pub fn schedule_named_after( - origin: OriginFor, - id: Vec, - after: T::BlockNumber, - maybe_periodic: Option>, - priority: schedule::Priority, - call: Box<::Call>, - ) -> DispatchResult { - T::ScheduleOrigin::ensure_origin(origin.clone())?; - let origin = ::Origin::from(origin); - Self::do_schedule_named( - id, - DispatchTime::After(after), - maybe_periodic, - priority, - origin.caller().clone(), - *call, - )?; - Ok(()) - } - } + #[pallet::call] + impl Pallet { + /// Anonymously schedule a task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 22.29 + .126 * S µs + /// - DB Weight: + /// - Read: Agenda + /// - Write: Agenda + /// - Will use base weight of 25 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule( + origin: OriginFor, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule( + DispatchTime::At(when), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Cancel an anonymously scheduled task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 22.15 + 2.869 * S µs + /// - DB Weight: + /// - Read: Agenda + /// - Write: Agenda, Lookup + /// - Will use base weight of 100 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] + pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_cancel(Some(origin.caller().clone()), (when, index))?; + Ok(()) + } + + /// Schedule a named task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 29.6 + .159 * S µs + /// - DB Weight: + /// - Read: Agenda, Lookup + /// - Write: Agenda, Lookup + /// - Will use base weight of 35 which should be good for more than 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named( + origin: OriginFor, + id: Vec, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::At(when), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Cancel a named scheduled task. + /// + /// # + /// - S = Number of already scheduled calls + /// - Base Weight: 24.91 + 2.907 * S µs + /// - DB Weight: + /// - Read: Agenda, Lookup + /// - Write: Agenda, Lookup + /// - Will use base weight of 100 which should be good for up to 30 scheduled calls + /// # + #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] + pub fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_cancel_named(Some(origin.caller().clone()), id)?; + Ok(()) + } + + /// Anonymously schedule a task after a delay. + /// + /// # + /// Same as [`schedule`]. + /// # + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule_after( + origin: OriginFor, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule( + DispatchTime::After(after), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + + /// Schedule a named task after a delay. + /// + /// # + /// Same as [`schedule_named`]. + /// # + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named_after( + origin: OriginFor, + id: Vec, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: schedule::Priority, + call: Box<::Call>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::Origin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::After(after), + maybe_periodic, + priority, + origin.caller().clone(), + *call, + )?; + Ok(()) + } + } } impl Pallet { - /// Migrate storage format from V1 to V2. - /// Return true if migration is performed. - pub fn migrate_v1_to_t2() -> bool { - if StorageVersion::::get() == Releases::V1 { - StorageVersion::::put(Releases::V2); - - Agenda::::translate::< - Vec::Call, T::BlockNumber>>>, - _, - >(|_, agenda| { - Some( - agenda - .into_iter() - .map(|schedule| { - schedule.map(|schedule| ScheduledV2 { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: system::RawOrigin::Root.into(), - _phantom: Default::default(), - }) - }) - .collect::>(), - ) - }); - - true - } else { - false - } - } - - /// Helper to migrate scheduler when the pallet origin type has changed. - pub fn migrate_origin + codec::Decode>() { - Agenda::::translate::< - Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, - _, - >(|_, agenda| { - Some( - agenda - .into_iter() - .map(|schedule| { - schedule.map(|schedule| Scheduled { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: schedule.origin.into(), - _phantom: Default::default(), - }) - }) - .collect::>(), - ) - }); - } - - fn resolve_time(when: DispatchTime) -> Result { - let now = frame_system::Pallet::::block_number(); - - let when = match when { - DispatchTime::At(x) => x, - // The current block has already completed it's scheduled tasks, so - // Schedule the task at lest one block after this current block. - DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()), - }; - - if when <= now { - return Err(Error::::TargetBlockNumberInPast.into()); - } - - Ok(when) - } - - fn do_schedule( - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result, DispatchError> { - let when = Self::resolve_time(when)?; - - // sanitize maybe_periodic - let maybe_periodic = maybe_periodic - .filter(|p| p.1 > 1 && !p.0.is_zero()) - // Remove one from the number of repetitions since we will schedule one now. - .map(|(p, c)| (p, c - 1)); - let s = Some(Scheduled { - maybe_id: None, - priority, - call, - maybe_periodic, - origin, - _phantom: PhantomData::::default(), - }); - Agenda::::append(when, s); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - Self::deposit_event(Event::Scheduled(when, index)); - - Ok((when, index)) - } - - fn do_cancel( - origin: Option, - (when, index): TaskAddress, - ) -> Result<(), DispatchError> { - let scheduled = Agenda::::try_mutate(when, |agenda| { - agenda.get_mut(index as usize).map_or( - Ok(None), - |s| -> Result>, DispatchError> { - if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { - return Err(BadOrigin.into()); - } - }; - Ok(s.take()) - }, - ) - })?; - if let Some(s) = scheduled { - if let Some(id) = s.maybe_id { - Lookup::::remove(id); - } - Self::deposit_event(Event::Canceled(when, index)); - Ok(()) - } else { - Err(Error::::NotFound)? - } - } - - fn do_reschedule( - (when, index): TaskAddress, - new_time: DispatchTime, - ) -> Result, DispatchError> { - let new_time = Self::resolve_time(new_time)?; - - if new_time == when { - return Err(Error::::RescheduleNoChange.into()); - } - - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); - Ok(()) - })?; - - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(Event::Canceled(when, index)); - Self::deposit_event(Event::Scheduled(new_time, new_index)); - - Ok((new_time, new_index)) - } - - fn do_schedule_named( - id: Vec, - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result, DispatchError> { - // ensure id it is unique - if Lookup::::contains_key(&id) { - return Err(Error::::FailedToSchedule)?; - } - - let when = Self::resolve_time(when)?; - - // sanitize maybe_periodic - let maybe_periodic = maybe_periodic - .filter(|p| p.1 > 1 && !p.0.is_zero()) - // Remove one from the number of repetitions since we will schedule one now. - .map(|(p, c)| (p, c - 1)); - - let s = Scheduled { - maybe_id: Some(id.clone()), - priority, - call, - maybe_periodic, - origin, - _phantom: Default::default(), - }; - Agenda::::append(when, Some(s)); - let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; - if index > T::MaxScheduledPerBlock::get() { - log::warn!( - target: "runtime::scheduler", - "Warning: There are more items queued in the Scheduler than \ - expected from the runtime configuration. An update might be needed.", - ); - } - let address = (when, index); - Lookup::::insert(&id, &address); - Self::deposit_event(Event::Scheduled(when, index)); - - Ok(address) - } - - fn do_cancel_named(origin: Option, id: Vec) -> DispatchResult { - Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { - if let Some((when, index)) = lookup.take() { - let i = index as usize; - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - if let Some(s) = agenda.get_mut(i) { - if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if *o != s.origin { - return Err(BadOrigin.into()); - } - } - *s = None; - } - Ok(()) - })?; - Self::deposit_event(Event::Canceled(when, index)); - Ok(()) - } else { - Err(Error::::NotFound)? - } - }) - } - - fn do_reschedule_named( - id: Vec, - new_time: DispatchTime, - ) -> Result, DispatchError> { - let new_time = Self::resolve_time(new_time)?; - - Lookup::::try_mutate_exists( - id, - |lookup| -> Result, DispatchError> { - let (when, index) = lookup.ok_or(Error::::NotFound)?; - - if new_time == when { - return Err(Error::::RescheduleNoChange.into()); - } - - Agenda::::try_mutate(when, |agenda| -> DispatchResult { - let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; - let task = task.take().ok_or(Error::::NotFound)?; - Agenda::::append(new_time, Some(task)); - - Ok(()) - })?; - - let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; - Self::deposit_event(Event::Canceled(when, index)); - Self::deposit_event(Event::Scheduled(new_time, new_index)); - - *lookup = Some((new_time, new_index)); - - Ok((new_time, new_index)) - }, - ) - } + /// Migrate storage format from V1 to V2. + /// Return true if migration is performed. + pub fn migrate_v1_to_t2() -> bool { + if StorageVersion::::get() == Releases::V1 { + StorageVersion::::put(Releases::V2); + + Agenda::::translate::< + Vec::Call, T::BlockNumber>>>, + _, + >(|_, agenda| { + Some( + agenda + .into_iter() + .map(|schedule| { + schedule.map(|schedule| ScheduledV2 { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: system::RawOrigin::Root.into(), + _phantom: Default::default(), + }) + }) + .collect::>(), + ) + }); + + true + } else { + false + } + } + + /// Helper to migrate scheduler when the pallet origin type has changed. + pub fn migrate_origin + codec::Decode>() { + Agenda::::translate::< + Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, + _, + >(|_, agenda| { + Some( + agenda + .into_iter() + .map(|schedule| { + schedule.map(|schedule| Scheduled { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin.into(), + _phantom: Default::default(), + }) + }) + .collect::>(), + ) + }); + } + + fn resolve_time(when: DispatchTime) -> Result { + let now = frame_system::Pallet::::block_number(); + + let when = match when { + DispatchTime::At(x) => x, + // The current block has already completed it's scheduled tasks, so + // Schedule the task at lest one block after this current block. + DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()), + }; + + if when <= now { + return Err(Error::::TargetBlockNumberInPast.into()); + } + + Ok(when) + } + + fn do_schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result, DispatchError> { + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + let s = Some(Scheduled { + maybe_id: None, + priority, + call, + maybe_periodic, + origin, + _phantom: PhantomData::::default(), + }); + Agenda::::append(when, s); + let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; + if index > T::MaxScheduledPerBlock::get() { + log::warn!( + target: "runtime::scheduler", + "Warning: There are more items queued in the Scheduler than \ + expected from the runtime configuration. An update might be needed.", + ); + } + Self::deposit_event(Event::Scheduled(when, index)); + + Ok((when, index)) + } + + fn do_cancel( + origin: Option, + (when, index): TaskAddress, + ) -> Result<(), DispatchError> { + let scheduled = Agenda::::try_mutate(when, |agenda| { + agenda.get_mut(index as usize).map_or( + Ok(None), + |s| -> Result>, DispatchError> { + if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { + if *o != s.origin { + return Err(BadOrigin.into()); + } + }; + Ok(s.take()) + }, + ) + })?; + if let Some(s) = scheduled { + if let Some(id) = s.maybe_id { + Lookup::::remove(id); + } + Self::deposit_event(Event::Canceled(when, index)); + Ok(()) + } else { + Err(Error::::NotFound)? + } + } + + fn do_reschedule( + (when, index): TaskAddress, + new_time: DispatchTime, + ) -> Result, DispatchError> { + let new_time = Self::resolve_time(new_time)?; + + if new_time == when { + return Err(Error::::RescheduleNoChange.into()); + } + + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; + let task = task.take().ok_or(Error::::NotFound)?; + Agenda::::append(new_time, Some(task)); + Ok(()) + })?; + + let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; + Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Scheduled(new_time, new_index)); + + Ok((new_time, new_index)) + } + + fn do_schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result, DispatchError> { + // ensure id it is unique + if Lookup::::contains_key(&id) { + return Err(Error::::FailedToSchedule)?; + } + + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + + let s = Scheduled { + maybe_id: Some(id.clone()), + priority, + call, + maybe_periodic, + origin, + _phantom: Default::default(), + }; + Agenda::::append(when, Some(s)); + let index = Agenda::::decode_len(when).unwrap_or(1) as u32 - 1; + if index > T::MaxScheduledPerBlock::get() { + log::warn!( + target: "runtime::scheduler", + "Warning: There are more items queued in the Scheduler than \ + expected from the runtime configuration. An update might be needed.", + ); + } + let address = (when, index); + Lookup::::insert(&id, &address); + Self::deposit_event(Event::Scheduled(when, index)); + + Ok(address) + } + + fn do_cancel_named(origin: Option, id: Vec) -> DispatchResult { + Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { + if let Some((when, index)) = lookup.take() { + let i = index as usize; + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + if let Some(s) = agenda.get_mut(i) { + if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { + if *o != s.origin { + return Err(BadOrigin.into()); + } + } + *s = None; + } + Ok(()) + })?; + Self::deposit_event(Event::Canceled(when, index)); + Ok(()) + } else { + Err(Error::::NotFound)? + } + }) + } + + fn do_reschedule_named( + id: Vec, + new_time: DispatchTime, + ) -> Result, DispatchError> { + let new_time = Self::resolve_time(new_time)?; + + Lookup::::try_mutate_exists( + id, + |lookup| -> Result, DispatchError> { + let (when, index) = lookup.ok_or(Error::::NotFound)?; + + if new_time == when { + return Err(Error::::RescheduleNoChange.into()); + } + + Agenda::::try_mutate(when, |agenda| -> DispatchResult { + let task = agenda.get_mut(index as usize).ok_or(Error::::NotFound)?; + let task = task.take().ok_or(Error::::NotFound)?; + Agenda::::append(new_time, Some(task)); + + Ok(()) + })?; + + let new_index = Agenda::::decode_len(new_time).unwrap_or(1) as u32 - 1; + Self::deposit_event(Event::Canceled(when, index)); + Self::deposit_event(Event::Scheduled(new_time, new_index)); + + *lookup = Some((new_time, new_index)); + + Ok((new_time, new_index)) + }, + ) + } } impl schedule::Anon::Call, T::PalletsOrigin> - for Pallet + for Pallet { - type Address = TaskAddress; - - fn schedule( - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result { - Self::do_schedule(when, maybe_periodic, priority, origin, call) - } - - fn cancel((when, index): Self::Address) -> Result<(), ()> { - Self::do_cancel(None, (when, index)).map_err(|_| ()) - } - - fn reschedule( - address: Self::Address, - when: DispatchTime, - ) -> Result { - Self::do_reschedule(address, when) - } - - fn next_dispatch_time((when, index): Self::Address) -> Result { - Agenda::::get(when) - .get(index as usize) - .ok_or(()) - .map(|_| when) - } + type Address = TaskAddress; + + fn schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result { + Self::do_schedule(when, maybe_periodic, priority, origin, call) + } + + fn cancel((when, index): Self::Address) -> Result<(), ()> { + Self::do_cancel(None, (when, index)).map_err(|_| ()) + } + + fn reschedule( + address: Self::Address, + when: DispatchTime, + ) -> Result { + Self::do_reschedule(address, when) + } + + fn next_dispatch_time((when, index): Self::Address) -> Result { + Agenda::::get(when) + .get(index as usize) + .ok_or(()) + .map(|_| when) + } } impl schedule::Named::Call, T::PalletsOrigin> - for Pallet + for Pallet { - type Address = TaskAddress; - - fn schedule_named( - id: Vec, - when: DispatchTime, - maybe_periodic: Option>, - priority: schedule::Priority, - origin: T::PalletsOrigin, - call: ::Call, - ) -> Result { - Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ()) - } - - fn cancel_named(id: Vec) -> Result<(), ()> { - Self::do_cancel_named(None, id).map_err(|_| ()) - } - - fn reschedule_named( - id: Vec, - when: DispatchTime, - ) -> Result { - Self::do_reschedule_named(id, when) - } - - fn next_dispatch_time(id: Vec) -> Result { - Lookup::::get(id) - .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) - .ok_or(()) - } + type Address = TaskAddress; + + fn schedule_named( + id: Vec, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ::Call, + ) -> Result { + Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ()) + } + + fn cancel_named(id: Vec) -> Result<(), ()> { + Self::do_cancel_named(None, id).map_err(|_| ()) + } + + fn reschedule_named( + id: Vec, + when: DispatchTime, + ) -> Result { + Self::do_reschedule_named(id, when) + } + + fn next_dispatch_time(id: Vec) -> Result { + Lookup::::get(id) + .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) + .ok_or(()) + } } #[cfg(test)] mod tests { - use super::*; - - use crate as scheduler; - use frame_support::{ - assert_err, assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{Filter, OnFinalize, OnInitialize}, - weights::constants::RocksDbWeight, - Hashable, - }; - use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, - }; - use substrate_test_utils::assert_eq_uvec; - - // Logger module to track execution. - #[frame_support::pallet] - pub mod logger { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use std::cell::RefCell; - - thread_local! { - static LOG: RefCell> = RefCell::new(Vec::new()); - } - pub fn log() -> Vec<(OriginCaller, u32)> { - LOG.with(|log| log.borrow().clone()) - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Logged(u32, Weight), - } - - #[pallet::call] - impl Pallet { - - #[pallet::weight(*weight)] - fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }); - Ok(()) - } - - #[pallet::weight(*weight)] - fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { - Self::deposit_event(Event::Logged(i, weight)); - LOG.with(|log| { - log.borrow_mut().push((origin.caller().clone(), i)); - }); - Ok(()) - } - } - } - - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Logger: logger::{Pallet, Call, Event}, - Scheduler: scheduler::{Pallet, Call, Storage, Event}, - } - ); - - // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. - pub struct BaseFilter; - impl Filter for BaseFilter { - fn filter(call: &Call) -> bool { - !matches!(call, Call::Logger(LoggerCall::log(_, _))) - } - } - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(2_000_000_000_000); - } - impl system::Config for Test { - type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - } - impl logger::Config for Test { - type Event = Event; - } - parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 10; - } - ord_parameter_types! { - pub const One: u64 = 1; - } - - impl Config for Test { - type Event = Event; - type Origin = Origin; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - } - - pub type LoggerCall = logger::Call; - - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::default() - .build_storage::() - .unwrap(); - t.into() - } - - fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } - } - - fn root() -> OriginCaller { - system::RawOrigin::Root.into() - } - - #[test] - fn basic_scheduling_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - call - )); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 - assert_ok!(Scheduler::do_schedule( - DispatchTime::After(3), - None, - 127, - root(), - call - )); - run_to_block(5); - assert!(logger::log().is_empty()); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn schedule_after_zero_works() { - new_test_ext().execute_with(|| { - run_to_block(2); - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::After(0), - None, - 127, - root(), - call - )); - // Will trigger on the next block. - run_to_block(3); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(LoggerCall::log(42, 1000)) - )); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); - run_to_block(100); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); - }); - } - - #[test] - fn reschedule_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!( - Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), - (6, 0) - ); - - assert_noop!( - Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); - - assert_noop!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); - - run_to_block(4); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - }); - } - - #[test] - fn reschedule_named_perodic_works() { - new_test_ext().execute_with(|| { - let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); - - run_to_block(3); - assert!(logger::log().is_empty()); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), - (5, 0) - ); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); - - run_to_block(5); - assert!(logger::log().is_empty()); - - run_to_block(6); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), - (10, 0) - ); - - run_to_block(9); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - - run_to_block(10); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - - run_to_block(13); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); - - run_to_block(100); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); - }); - } - - #[test] - fn cancel_named_scheduling_works_with_normal_cancel() { - new_test_ext().execute_with(|| { - // at #4. - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, 1000)), - ) - .unwrap(); - let i = Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(42, 1000)), - ) - .unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn cancel_named_periodic_scheduling_works() { - new_test_ext().execute_with(|| { - // at #4, every 3 blocks, 3 times. - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(LoggerCall::log(42, 1000)), - ) - .unwrap(); - // same id results in error. - assert!(Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, 1000)) - ) - .is_err()); - // different id is ok. - Scheduler::do_schedule_named( - 2u32.encode(), - DispatchTime::At(8), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, 1000)), - ) - .unwrap(); - run_to_block(3); - assert!(logger::log().is_empty()); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); - assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); - run_to_block(100); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_weight_limits() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // 69 and 42 do not fit together - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_hard_deadlines_more() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // With base weights, 69 and 42 should not fit together, but do because of hard deadlines - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); - }); - } - - #[test] - fn scheduler_respects_priority_ordering() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 1, - root(), - Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 0, - root(), - Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) - )); - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); - }); - } - - #[test] - fn scheduler_respects_priority_ordering_with_soft_deadlines() { - new_test_ext().execute_with(|| { - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 255, - root(), - Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 126, - root(), - Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) - )); - - // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - // 69 and 42 fit together - run_to_block(5); - assert_eq!( - logger::log(), - vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] - ); - }); - } - - #[test] - fn on_initialize_weight_is_correct() { - new_test_ext().execute_with(|| { - let base_weight: Weight = - ::DbWeight::get().reads_writes(1, 2); - let base_multiplier = 0; - let named_multiplier = ::DbWeight::get().writes(1); - let periodic_multiplier = - ::DbWeight::get().reads_writes(1, 1); - - // Named - assert_ok!(Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(1), - None, - 255, - root(), - Call::Logger(LoggerCall::log(3, MaximumSchedulerWeight::get() / 3)) - )); - // Anon Periodic - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - Some((1000, 3)), - 128, - root(), - Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) - )); - // Anon - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(1), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) - )); - // Named Periodic - assert_ok!(Scheduler::do_schedule_named( - 2u32.encode(), - DispatchTime::At(1), - Some((1000, 3)), - 126, - root(), - Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) - )); - - // Will include the named periodic only - let actual_weight = Scheduler::on_initialize(1); - let call_weight = MaximumSchedulerWeight::get() / 2; - assert_eq!( - actual_weight, - call_weight - + base_weight - + base_multiplier - + named_multiplier - + periodic_multiplier - ); - assert_eq!(logger::log(), vec![(root(), 2600u32)]); - - // Will include anon and anon periodic - let actual_weight = Scheduler::on_initialize(2); - let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3; - assert_eq!( - actual_weight, - call_weight + base_weight + base_multiplier * 2 + periodic_multiplier - ); - assert_eq!( - logger::log(), - vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] - ); - - // Will include named only - let actual_weight = Scheduler::on_initialize(3); - let call_weight = MaximumSchedulerWeight::get() / 3; - assert_eq!( - actual_weight, - call_weight + base_weight + base_multiplier + named_multiplier - ); - assert_eq!( - logger::log(), - vec![ - (root(), 2600u32), - (root(), 69u32), - (root(), 42u32), - (root(), 3u32) - ] - ); - - // Will contain none - let actual_weight = Scheduler::on_initialize(4); - assert_eq!(actual_weight, 0); - }); - } - - #[test] - fn root_calls_works() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); - let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); - assert_ok!(Scheduler::schedule_named( - Origin::root(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2)); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode())); - assert_ok!(Scheduler::cancel(Origin::root(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn fails_to_schedule_task_in_the_past() { - new_test_ext().execute_with(|| { - run_to_block(3); - - let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); - let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); - - assert_err!( - Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call), - Error::::TargetBlockNumberInPast, - ); - - assert_err!( - Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()), - Error::::TargetBlockNumberInPast, - ); - - assert_err!( - Scheduler::schedule(Origin::root(), 3, None, 127, call2), - Error::::TargetBlockNumberInPast, - ); - }); - } - - #[test] - fn should_use_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); - let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); - assert_ok!(Scheduler::schedule_named( - system::RawOrigin::Signed(1).into(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule( - system::RawOrigin::Signed(1).into(), - 4, - None, - 127, - call2 - )); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_ok!(Scheduler::cancel_named( - system::RawOrigin::Signed(1).into(), - 1u32.encode() - )); - assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); - // Scheduled calls are made NONE, so should not effect state - run_to_block(100); - assert!(logger::log().is_empty()); - }); - } - - #[test] - fn should_check_orign() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); - let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); - assert_noop!( - Scheduler::schedule_named( - system::RawOrigin::Signed(2).into(), - 1u32.encode(), - 4, - None, - 127, - call - ), - BadOrigin - ); - assert_noop!( - Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), - BadOrigin - ); - }); - } - - #[test] - fn should_check_orign_for_cancel() { - new_test_ext().execute_with(|| { - let call = Box::new(Call::Logger(LoggerCall::log_without_filter(69, 1000))); - let call2 = Box::new(Call::Logger(LoggerCall::log_without_filter(42, 1000))); - assert_ok!(Scheduler::schedule_named( - system::RawOrigin::Signed(1).into(), - 1u32.encode(), - 4, - None, - 127, - call - )); - assert_ok!(Scheduler::schedule( - system::RawOrigin::Signed(1).into(), - 4, - None, - 127, - call2 - )); - run_to_block(3); - // Scheduled calls are in the agenda. - assert_eq!(Agenda::::get(4).len(), 2); - assert!(logger::log().is_empty()); - assert_noop!( - Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), - BadOrigin - ); - assert_noop!( - Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), - BadOrigin - ); - assert_noop!( - Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), - BadOrigin - ); - assert_noop!( - Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), - BadOrigin - ); - run_to_block(5); - assert_eq!( - logger::log(), - vec![ - (system::RawOrigin::Signed(1).into(), 69u32), - (system::RawOrigin::Signed(1).into(), 42u32) - ] - ); - }); - } - - #[test] - fn migration_to_v2_works() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old = vec![ - Some(ScheduledV1 { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - }), - None, - Some(ScheduledV1 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - }), - ]; - frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); - } - - assert_eq!(StorageVersion::::get(), Releases::V1); - - assert!(Scheduler::migrate_v1_to_t2()); - - assert_eq_uvec!( - Agenda::::iter().collect::>(), - vec![ - ( - 0, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 10, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: root(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: root(), - _phantom: PhantomData::::default(), - }), - ] - ) - ] - ); - - assert_eq!(StorageVersion::::get(), Releases::V2); - }); - } - - #[test] - fn test_migrate_origin() { - new_test_ext().execute_with(|| { - for i in 0..3u64 { - let k = i.twox_64_concat(); - let old: Vec>> = vec![ - Some(Scheduled { - maybe_id: None, - priority: i as u8 + 10, - call: Call::Logger(LoggerCall::log(96, 100)), - origin: 3u32, - maybe_periodic: None, - _phantom: Default::default(), - }), - None, - Some(Scheduled { - maybe_id: Some(b"test".to_vec()), - priority: 123, - origin: 2u32, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - _phantom: Default::default(), - }), - ]; - frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); - } - - impl Into for u32 { - fn into(self) -> OriginCaller { - match self { - 3u32 => system::RawOrigin::Root.into(), - 2u32 => system::RawOrigin::None.into(), - _ => unreachable!("test make no use of it"), - } - } - } - - Scheduler::migrate_origin::(); - - assert_eq_uvec!( - Agenda::::iter().collect::>(), - vec![ - ( - 0, - vec![ - Some(ScheduledV2::<_, _, OriginCaller, u64> { - maybe_id: None, - priority: 10, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 1, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 11, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ), - ( - 2, - vec![ - Some(ScheduledV2 { - maybe_id: None, - priority: 12, - call: Call::Logger(LoggerCall::log(96, 100)), - maybe_periodic: None, - origin: system::RawOrigin::Root.into(), - _phantom: PhantomData::::default(), - }), - None, - Some(ScheduledV2 { - maybe_id: Some(b"test".to_vec()), - priority: 123, - call: Call::Logger(LoggerCall::log(69, 1000)), - maybe_periodic: Some((456u64, 10)), - origin: system::RawOrigin::None.into(), - _phantom: PhantomData::::default(), - }), - ] - ) - ] - ); - }); - } + use super::*; + + use crate as scheduler; + use frame_support::{ + assert_err, assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{Filter, OnFinalize, OnInitialize}, + weights::constants::RocksDbWeight, + Hashable, + }; + use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy}; + use sp_core::H256; + use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, + }; + use substrate_test_utils::assert_eq_uvec; + + // Logger module to track execution. + #[frame_support::pallet] + pub mod logger { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use std::cell::RefCell; + + thread_local! { + static LOG: RefCell> = RefCell::new(Vec::new()); + } + pub fn log() -> Vec<(OriginCaller, u32)> { + LOG.with(|log| log.borrow().clone()) + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Logged(u32, Weight), + } + + #[pallet::call] + impl Pallet where ::Origin: OriginTrait { + #[pallet::weight(*weight)] + fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + LOG.with(|log| { + log.borrow_mut().push((origin.caller().clone(), i)); + }); + Ok(()) + } + + #[pallet::weight(*weight)] + fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + LOG.with(|log| { + log.borrow_mut().push((origin.caller().clone(), i)); + }); + Ok(()) + } + } + } + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Logger: logger::{Pallet, Call, Event}, + Scheduler: scheduler::{Pallet, Call, Storage, Event}, + } + ); + + // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. + pub struct BaseFilter; + impl Filter for BaseFilter { + fn filter(call: &Call) -> bool { + !matches!(call, Call::Logger(LoggerCall::log(_, _))) + } + } + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(2_000_000_000_000); + } + impl system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + } + impl logger::Config for Test { + type Event = Event; + } + parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 10; + } + ord_parameter_types! { + pub const One: u64 = 1; + } + + impl Config for Test { + type Event = Event; + type Origin = Origin; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = (); + } + + pub type LoggerCall = logger::Call; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + t.into() + } + + fn run_to_block(n: u64) { + while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + } + } + + fn root() -> OriginCaller { + system::RawOrigin::Root.into() + } + + #[test] + fn basic_scheduling_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + call + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn schedule_after_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(3), + None, + 127, + root(), + call + )); + run_to_block(5); + assert!(logger::log().is_empty()); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn schedule_after_zero_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(0), + None, + 127, + root(), + call + )); + // Will trigger on the next block. + run_to_block(3); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)) + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(7); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(10); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + run_to_block(100); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn reschedule_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + assert_noop!( + Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn reschedule_named_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + call + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + assert_noop!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), + Error::::RescheduleNoChange + ); + + run_to_block(4); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); + } + + #[test] + fn reschedule_named_perodic_works() { + new_test_ext().execute_with(|| { + let call = Call::Logger(LoggerCall::log(42, 1000)); + assert!(!::BaseCallFilter::filter( + &call + )); + assert_eq!( + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + call + ) + .unwrap(), + (4, 0) + ); + + run_to_block(3); + assert!(logger::log().is_empty()); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), + (5, 0) + ); + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), + (6, 0) + ); + + run_to_block(5); + assert!(logger::log().is_empty()); + + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + assert_eq!( + Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), + (10, 0) + ); + + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + + run_to_block(13); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + + run_to_block(100); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn cancel_named_scheduling_works_with_normal_cancel() { + new_test_ext().execute_with(|| { + // at #4. + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)), + ) + .unwrap(); + let i = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); + assert_ok!(Scheduler::do_cancel(None, i)); + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn cancel_named_periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + Call::Logger(LoggerCall::log(42, 1000)), + ) + .unwrap(); + // same id results in error. + assert!(Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)) + ) + .is_err()); + // different id is ok. + Scheduler::do_schedule_named( + 2u32.encode(), + DispatchTime::At(8), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, 1000)), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_weight_limits() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // 69 and 42 do not fit together + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_hard_deadlines_more() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // With base weights, 69 and 42 should not fit together, but do because of hard deadlines + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 1, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); + }); + } + + #[test] + fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 255, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 126, + root(), + Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) + )); + + // 2600 does not fit with 69 or 42, but has higher priority, so will go through + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + // 69 and 42 fit together + run_to_block(5); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + }); + } + + #[test] + fn on_initialize_weight_is_correct() { + new_test_ext().execute_with(|| { + let base_weight: Weight = + ::DbWeight::get().reads_writes(1, 2); + let base_multiplier = 0; + let named_multiplier = ::DbWeight::get().writes(1); + let periodic_multiplier = + ::DbWeight::get().reads_writes(1, 1); + + // Named + assert_ok!(Scheduler::do_schedule_named( + 1u32.encode(), + DispatchTime::At(1), + None, + 255, + root(), + Call::Logger(LoggerCall::log(3, MaximumSchedulerWeight::get() / 3)) + )); + // Anon Periodic + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(1), + Some((1000, 3)), + 128, + root(), + Call::Logger(LoggerCall::log(42, MaximumSchedulerWeight::get() / 3)) + )); + // Anon + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(1), + None, + 127, + root(), + Call::Logger(LoggerCall::log(69, MaximumSchedulerWeight::get() / 2)) + )); + // Named Periodic + assert_ok!(Scheduler::do_schedule_named( + 2u32.encode(), + DispatchTime::At(1), + Some((1000, 3)), + 126, + root(), + Call::Logger(LoggerCall::log(2600, MaximumSchedulerWeight::get() / 2)) + )); + + // Will include the named periodic only + let actual_weight = Scheduler::on_initialize(1); + let call_weight = MaximumSchedulerWeight::get() / 2; + assert_eq!( + actual_weight, + call_weight + + base_weight + base_multiplier + + named_multiplier + periodic_multiplier + ); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + + // Will include anon and anon periodic + let actual_weight = Scheduler::on_initialize(2); + let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3; + assert_eq!( + actual_weight, + call_weight + base_weight + base_multiplier * 2 + periodic_multiplier + ); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + + // Will include named only + let actual_weight = Scheduler::on_initialize(3); + let call_weight = MaximumSchedulerWeight::get() / 3; + assert_eq!( + actual_weight, + call_weight + base_weight + base_multiplier + named_multiplier + ); + assert_eq!( + logger::log(), + vec![ + (root(), 2600u32), + (root(), 69u32), + (root(), 42u32), + (root(), 3u32) + ] + ); + + // Will contain none + let actual_weight = Scheduler::on_initialize(4); + assert_eq!(actual_weight, 0); + }); + } + + #[test] + fn root_calls_works() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_ok!(Scheduler::schedule_named( + Origin::root(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2)); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode())); + assert_ok!(Scheduler::cancel(Origin::root(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn fails_to_schedule_task_in_the_past() { + new_test_ext().execute_with(|| { + run_to_block(3); + + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + + assert_err!( + Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call), + Error::::TargetBlockNumberInPast, + ); + + assert_err!( + Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()), + Error::::TargetBlockNumberInPast, + ); + + assert_err!( + Scheduler::schedule(Origin::root(), 3, None, 127, call2), + Error::::TargetBlockNumberInPast, + ); + }); + } + + #[test] + fn should_use_orign() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + 127, + call2 + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode() + )); + assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); + } + + #[test] + fn should_check_orign() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log(42, 1000))); + assert_noop!( + Scheduler::schedule_named( + system::RawOrigin::Signed(2).into(), + 1u32.encode(), + 4, + None, + 127, + call + ), + BadOrigin + ); + assert_noop!( + Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), + BadOrigin + ); + }); + } + + #[test] + fn should_check_orign_for_cancel() { + new_test_ext().execute_with(|| { + let call = Box::new(Call::Logger(LoggerCall::log_without_filter(69, 1000))); + let call2 = Box::new(Call::Logger(LoggerCall::log_without_filter(42, 1000))); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + 1u32.encode(), + 4, + None, + 127, + call + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + 127, + call2 + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), + BadOrigin + ); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), + BadOrigin + ); + run_to_block(5); + assert_eq!( + logger::log(), + vec![ + (system::RawOrigin::Signed(1).into(), 69u32), + (system::RawOrigin::Signed(1).into(), 42u32) + ] + ); + }); + } + + #[test] + fn migration_to_v2_works() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old = vec![ + Some(ScheduledV1 { + maybe_id: None, + priority: i as u8 + 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + }), + None, + Some(ScheduledV1 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + assert_eq!(StorageVersion::::get(), Releases::V1); + + assert!(Scheduler::migrate_v1_to_t2()); + + assert_eq_uvec!( + Agenda::::iter().collect::>(), + vec![ + ( + 0, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 1, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 11, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 2, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 12, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: root(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: root(), + _phantom: PhantomData::::default(), + }), + ] + ) + ] + ); + + assert_eq!(StorageVersion::::get(), Releases::V2); + }); + } + + #[test] + fn test_migrate_origin() { + new_test_ext().execute_with(|| { + for i in 0..3u64 { + let k = i.twox_64_concat(); + let old: Vec>> = vec![ + Some(Scheduled { + maybe_id: None, + priority: i as u8 + 10, + call: Call::Logger(LoggerCall::log(96, 100)), + origin: 3u32, + maybe_periodic: None, + _phantom: Default::default(), + }), + None, + Some(Scheduled { + maybe_id: Some(b"test".to_vec()), + priority: 123, + origin: 2u32, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + _phantom: Default::default(), + }), + ]; + frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old); + } + + impl Into for u32 { + fn into(self) -> OriginCaller { + match self { + 3u32 => system::RawOrigin::Root.into(), + 2u32 => system::RawOrigin::None.into(), + _ => unreachable!("test make no use of it"), + } + } + } + + Scheduler::migrate_origin::(); + + assert_eq_uvec!( + Agenda::::iter().collect::>(), + vec![ + ( + 0, + vec![ + Some(ScheduledV2::<_, _, OriginCaller, u64> { + maybe_id: None, + priority: 10, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 1, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 11, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ), + ( + 2, + vec![ + Some(ScheduledV2 { + maybe_id: None, + priority: 12, + call: Call::Logger(LoggerCall::log(96, 100)), + maybe_periodic: None, + origin: system::RawOrigin::Root.into(), + _phantom: PhantomData::::default(), + }), + None, + Some(ScheduledV2 { + maybe_id: Some(b"test".to_vec()), + priority: 123, + call: Call::Logger(LoggerCall::log(69, 1000)), + maybe_periodic: Some((456u64, 10)), + origin: system::RawOrigin::None.into(), + _phantom: PhantomData::::default(), + }), + ] + ) + ] + ); + }); + } } From fd8986865d8e6862c0d928e12aa8c698a60876bf Mon Sep 17 00:00:00 2001 From: Stanly Johnson Date: Mon, 10 May 2021 00:46:06 +0530 Subject: [PATCH 03/10] fix genesis --- frame/scheduler/src/benchmarking.rs | 2 +- frame/scheduler/src/lib.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index 563a1ba89c86f..47375658fb9bc 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -25,7 +25,7 @@ use frame_system::RawOrigin; use frame_support::{ensure, traits::OnInitialize}; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use crate::Module as Scheduler; +use crate::Pallet as Scheduler; use frame_system::Pallet as System; const BLOCK_NUMBER: u32 = 2; diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index a9345dc6ab17c..7e7d279700d40 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -167,7 +167,7 @@ pub mod pallet { /// Items to be executed, indexed by the block number that they should be executed on. #[pallet::storage] - pub(super) type Agenda = StorageMap< + pub type Agenda = StorageMap< _, Twox64Concat, T::BlockNumber, @@ -207,6 +207,23 @@ pub mod pallet { RescheduleNoChange, } + #[pallet::genesis_config] + pub struct GenesisConfig; + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self{ + Self + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + StorageVersion::::put(Releases::V2); + } + } + #[pallet::hooks] impl Hooks> for Pallet { /// Execute the scheduled calls From 861ddba2d84db6809b3248b685b693259a7c75ee Mon Sep 17 00:00:00 2001 From: Stanly Johnson Date: Wed, 12 May 2021 23:01:24 +0530 Subject: [PATCH 04/10] code review fixes --- frame/scheduler/src/lib.rs | 231 ++++++++++--------------------------- 1 file changed, 63 insertions(+), 168 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 7e7d279700d40..357b739f1a19e 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -126,10 +126,6 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); - /// Our pallet's configuration trait. All our types and constants go in here. If the - /// pallet is dependent on specific other pallets, then their configuration traits - /// should be added to our implied traits list. - /// /// `system::Config` should always be included in our implied traits. #[pallet::config] pub trait Config: frame_system::Config { @@ -346,7 +342,7 @@ pub mod pallet { /// - Will use base weight of 25 which should be good for up to 30 scheduled calls /// # #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] - pub fn schedule( + pub(crate) fn schedule( origin: OriginFor, when: T::BlockNumber, maybe_periodic: Option>, @@ -376,7 +372,7 @@ pub mod pallet { /// - Will use base weight of 100 which should be good for up to 30 scheduled calls /// # #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] - pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { + pub(crate) fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::Origin::from(origin); Self::do_cancel(Some(origin.caller().clone()), (when, index))?; @@ -394,7 +390,7 @@ pub mod pallet { /// - Will use base weight of 35 which should be good for more than 30 scheduled calls /// # #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] - pub fn schedule_named( + pub(crate) fn schedule_named( origin: OriginFor, id: Vec, when: T::BlockNumber, @@ -426,7 +422,7 @@ pub mod pallet { /// - Will use base weight of 100 which should be good for up to 30 scheduled calls /// # #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] - pub fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { + pub(crate) fn cancel_named(origin: OriginFor, id: Vec) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::Origin::from(origin); Self::do_cancel_named(Some(origin.caller().clone()), id)?; @@ -439,7 +435,7 @@ pub mod pallet { /// Same as [`schedule`]. /// # #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] - pub fn schedule_after( + pub(crate) fn schedule_after( origin: OriginFor, after: T::BlockNumber, maybe_periodic: Option>, @@ -464,7 +460,7 @@ pub mod pallet { /// Same as [`schedule_named`]. /// # #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] - pub fn schedule_named_after( + pub(crate) fn schedule_named_after( origin: OriginFor, id: Vec, after: T::BlockNumber, @@ -495,25 +491,20 @@ impl Pallet { StorageVersion::::put(Releases::V2); Agenda::::translate::< - Vec::Call, T::BlockNumber>>>, - _, - >(|_, agenda| { - Some( - agenda - .into_iter() - .map(|schedule| { - schedule.map(|schedule| ScheduledV2 { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: system::RawOrigin::Root.into(), - _phantom: Default::default(), - }) - }) - .collect::>(), - ) - }); + Vec::Call, T::BlockNumber>>>, _ + >(|_, agenda| Some( + agenda + .into_iter() + .map(|schedule| schedule.map(|schedule| ScheduledV2 { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: system::RawOrigin::Root.into(), + _phantom: Default::default(), + })) + .collect::>() + )); true } else { @@ -524,25 +515,20 @@ impl Pallet { /// Helper to migrate scheduler when the pallet origin type has changed. pub fn migrate_origin + codec::Decode>() { Agenda::::translate::< - Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, - _, - >(|_, agenda| { - Some( - agenda - .into_iter() - .map(|schedule| { - schedule.map(|schedule| Scheduled { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: schedule.origin.into(), - _phantom: Default::default(), - }) - }) - .collect::>(), - ) - }); + Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _ + >(|_, agenda| Some( + agenda + .into_iter() + .map(|schedule| schedule.map(|schedule| Scheduled { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin.into(), + _phantom: Default::default(), + })) + .collect::>() + )); } fn resolve_time(when: DispatchTime) -> Result { @@ -974,9 +960,7 @@ mod tests { pub type LoggerCall = logger::Call; pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::default() - .build_storage::() - .unwrap(); + let t = system::GenesisConfig::default().build_storage::().unwrap(); t.into() } @@ -999,13 +983,7 @@ mod tests { assert!(!::BaseCallFilter::filter( &call )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - call - )); + assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call)); run_to_block(3); assert!(logger::log().is_empty()); run_to_block(4); @@ -1024,13 +1002,7 @@ mod tests { &call )); // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 - assert_ok!(Scheduler::do_schedule( - DispatchTime::After(3), - None, - 127, - root(), - call - )); + assert_ok!(Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call)); run_to_block(5); assert!(logger::log().is_empty()); run_to_block(6); @@ -1045,16 +1017,8 @@ mod tests { new_test_ext().execute_with(|| { run_to_block(2); let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_ok!(Scheduler::do_schedule( - DispatchTime::After(0), - None, - 127, - root(), - call - )); + assert!(!::BaseCallFilter::filter(&call)); + assert_ok!(Scheduler::do_schedule(DispatchTime::After(0), None, 127, root(), call)); // Will trigger on the next block. run_to_block(3); assert_eq!(logger::log(), vec![(root(), 42u32)]); @@ -1068,11 +1032,7 @@ mod tests { new_test_ext().execute_with(|| { // at #4, every 3 blocks, 3 times. assert_ok!(Scheduler::do_schedule( - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - Call::Logger(LoggerCall::log(42, 1000)) + DispatchTime::At(4), Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000)) )); run_to_block(3); assert!(logger::log().is_empty()); @@ -1101,26 +1061,15 @@ mod tests { fn reschedule_works() { new_test_ext().execute_with(|| { let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), - (4, 0) - ); + assert!(!::BaseCallFilter::filter(&call)); + assert_eq!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), (4, 0)); run_to_block(3); assert!(logger::log().is_empty()); - assert_eq!( - Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), - (6, 0) - ); + assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); - assert_noop!( - Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); + assert_noop!(Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), Error::::RescheduleNoChange); run_to_block(4); assert!(logger::log().is_empty()); @@ -1137,34 +1086,17 @@ mod tests { fn reschedule_named_works() { new_test_ext().execute_with(|| { let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); + assert!(!::BaseCallFilter::filter(&call)); + assert_eq!(Scheduler::do_schedule_named( + 1u32.encode(), DispatchTime::At(4), None, 127, root(), call + ).unwrap(), (4, 0)); run_to_block(3); assert!(logger::log().is_empty()); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); + assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0)); - assert_noop!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), - Error::::RescheduleNoChange - ); + assert_noop!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), Error::::RescheduleNoChange); run_to_block(4); assert!(logger::log().is_empty()); @@ -1181,33 +1113,16 @@ mod tests { fn reschedule_named_perodic_works() { new_test_ext().execute_with(|| { let call = Call::Logger(LoggerCall::log(42, 1000)); - assert!(!::BaseCallFilter::filter( - &call - )); - assert_eq!( - Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - Some((3, 3)), - 127, - root(), - call - ) - .unwrap(), - (4, 0) - ); + assert!(!::BaseCallFilter::filter(&call)); + assert_eq!(Scheduler::do_schedule_named( + 1u32.encode(), DispatchTime::At(4), Some((3, 3)), 127, root(), call + ).unwrap(), (4, 0)); run_to_block(3); assert!(logger::log().is_empty()); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), - (5, 0) - ); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), - (6, 0) - ); + assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), (5, 0)); + assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0)); run_to_block(5); assert!(logger::log().is_empty()); @@ -1215,10 +1130,7 @@ mod tests { run_to_block(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - assert_eq!( - Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), - (10, 0) - ); + assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), (10, 0)); run_to_block(9); assert_eq!(logger::log(), vec![(root(), 42u32)]); @@ -1227,16 +1139,10 @@ mod tests { assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); run_to_block(13); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); run_to_block(100); - assert_eq!( - logger::log(), - vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] - ); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -1245,22 +1151,11 @@ mod tests { new_test_ext().execute_with(|| { // at #4. Scheduler::do_schedule_named( - 1u32.encode(), - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(69, 1000)), - ) - .unwrap(); + 1u32.encode(), DispatchTime::At(4), None, 127, root(), Call::Logger(LoggerCall::log(69, 1000)) + ).unwrap(); let i = Scheduler::do_schedule( - DispatchTime::At(4), - None, - 127, - root(), - Call::Logger(LoggerCall::log(42, 1000)), - ) - .unwrap(); + DispatchTime::At(4), None, 127, root(), Call::Logger(LoggerCall::log(42, 1000)) + ).unwrap(); run_to_block(3); assert!(logger::log().is_empty()); assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode())); From 79d6bfbfa66df7a730aaae33ac66a418e6545e11 Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Sat, 15 May 2021 10:16:26 +0530 Subject: [PATCH 05/10] Update frame/scheduler/src/lib.rs Co-authored-by: Keith Yeung --- frame/scheduler/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 357b739f1a19e..ec52b494e47ff 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -176,6 +176,9 @@ pub mod pallet { pub(crate) type Lookup = StorageMap<_, Twox64Concat, Vec, TaskAddress>; + /// Storage version of the pallet. + /// + /// New networks start with last version. #[pallet::storage] pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; From 447b062aefe4a8d2c1f05d85a4a3b476f5651c2d Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Sat, 15 May 2021 10:16:40 +0530 Subject: [PATCH 06/10] Update frame/scheduler/src/lib.rs Co-authored-by: Keith Yeung --- frame/scheduler/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ec52b494e47ff..cc10d1ecd914e 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -211,9 +211,9 @@ pub mod pallet { #[cfg(feature = "std")] impl Default for GenesisConfig { - fn default() -> Self{ - Self - } + fn default() -> Self { + Self + } } #[pallet::genesis_build] From 197734816d69c4ae190235f972ed73b719fbd1dd Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Sat, 15 May 2021 10:17:00 +0530 Subject: [PATCH 07/10] Update frame/scheduler/src/lib.rs Co-authored-by: Keith Yeung --- frame/scheduler/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index cc10d1ecd914e..e8781f4f8f3cb 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -185,6 +185,7 @@ pub mod pallet { /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::BlockNumber = "BlockNumber")] pub enum Event { /// Scheduled some task. \[when, index\] Scheduled(T::BlockNumber, u32), From 62517dcd0eaab620eb1e31f379b1d63e83d068f1 Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Sat, 15 May 2021 10:17:14 +0530 Subject: [PATCH 08/10] Update frame/scheduler/src/lib.rs Co-authored-by: Keith Yeung --- frame/scheduler/src/lib.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index e8781f4f8f3cb..14d18fc09c919 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -495,20 +495,20 @@ impl Pallet { StorageVersion::::put(Releases::V2); Agenda::::translate::< - Vec::Call, T::BlockNumber>>>, _ - >(|_, agenda| Some( - agenda - .into_iter() - .map(|schedule| schedule.map(|schedule| ScheduledV2 { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: system::RawOrigin::Root.into(), - _phantom: Default::default(), - })) - .collect::>() - )); + Vec::Call, T::BlockNumber>>>, _ + >(|_, agenda| Some( + agenda + .into_iter() + .map(|schedule| schedule.map(|schedule| ScheduledV2 { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: system::RawOrigin::Root.into(), + _phantom: Default::default(), + })) + .collect::>() + )); true } else { From f77e344b25351a22743c8d69d9d194427301b078 Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Sat, 15 May 2021 10:17:24 +0530 Subject: [PATCH 09/10] Update frame/scheduler/src/lib.rs Co-authored-by: Keith Yeung --- frame/scheduler/src/lib.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 14d18fc09c919..954b4d6c9de76 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -519,20 +519,20 @@ impl Pallet { /// Helper to migrate scheduler when the pallet origin type has changed. pub fn migrate_origin + codec::Decode>() { Agenda::::translate::< - Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _ - >(|_, agenda| Some( - agenda - .into_iter() - .map(|schedule| schedule.map(|schedule| Scheduled { - maybe_id: schedule.maybe_id, - priority: schedule.priority, - call: schedule.call, - maybe_periodic: schedule.maybe_periodic, - origin: schedule.origin.into(), - _phantom: Default::default(), - })) - .collect::>() - )); + Vec::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _ + >(|_, agenda| Some( + agenda + .into_iter() + .map(|schedule| schedule.map(|schedule| Scheduled { + maybe_id: schedule.maybe_id, + priority: schedule.priority, + call: schedule.call, + maybe_periodic: schedule.maybe_periodic, + origin: schedule.origin.into(), + _phantom: Default::default(), + })) + .collect::>() + )); } fn resolve_time(when: DispatchTime) -> Result { From 1bb2fb0f2679903527c0abce62bd73ece6737c2c Mon Sep 17 00:00:00 2001 From: Stanly Johnson Date: Mon, 17 May 2021 15:55:45 +0530 Subject: [PATCH 10/10] fix metadata --- frame/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 954b4d6c9de76..006ab5a0f2d75 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -185,7 +185,7 @@ pub mod pallet { /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - #[pallet::metadata(T::BlockNumber = "BlockNumber")] + #[pallet::metadata(T::BlockNumber = "BlockNumber", TaskAddress = "TaskAddress")] pub enum Event { /// Scheduled some task. \[when, index\] Scheduled(T::BlockNumber, u32),