// This file is part of Substrate. // Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! # Scheduler //! A Pallet for scheduling dispatches. //! //! - [`Config`] //! - [`Call`] //! - [`Pallet`] //! //! ## Overview //! //! 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. //! //! **NOTE:** The scheduled calls will be dispatched with the default filter //! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin //! except root which will get no filter. And not the filter contained in origin //! use to call `fn schedule`. //! //! If a call is scheduled using proxy or whatever mecanism which adds filter, //! then those filter will not be used when dispatching the schedule call. //! //! ## Interface //! //! ### Dispatchable Functions //! //! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and //! with a specified priority. //! * `cancel` - cancel a scheduled dispatch, specified by block number and index. //! * `schedule_named` - augments the `schedule` interface with an additional `Vec` parameter //! that can be used for identification. //! * `cancel_named` - the named complement to the cancel function. // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; pub mod weights; use codec::{Codec, Decode, Encode}; use frame_support::{ dispatch::{DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter}, traits::{ schedule::{self, DispatchTime, MaybeHashed}, EnsureOrigin, Get, IsType, OriginTrait, PalletInfoAccess, PrivilegeCmp, StorageVersion, }, weights::Weight, }; use frame_system::{self as system, ensure_signed}; pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::{ traits::{BadOrigin, One, Saturating, Zero}, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. pub type PeriodicIndex = u32; /// The location of a scheduled task that can be used to remove it. pub type TaskAddress = (BlockNumber, u32); pub type CallOrHashOf = MaybeHashed<::RuntimeCall, ::Hash>; #[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>, } /// Information regarding an item to be executed in the future. #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)] pub struct ScheduledV3 { /// 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, } use crate::ScheduledV3 as ScheduledV2; pub type ScheduledV2Of = ScheduledV3< ::RuntimeCall, ::BlockNumber, ::PalletsOrigin, ::AccountId, >; pub type ScheduledV3Of = ScheduledV3< CallOrHashOf, ::BlockNumber, ::PalletsOrigin, ::AccountId, >; pub type ScheduledOf = ScheduledV3Of; /// The current version of Scheduled struct. pub type Scheduled = ScheduledV2; #[cfg(feature = "runtime-benchmarks")] mod preimage_provider { use frame_support::traits::PreimageRecipient; pub trait PreimageProviderAndMaybeRecipient: PreimageRecipient {} impl> PreimageProviderAndMaybeRecipient for T {} } #[cfg(not(feature = "runtime-benchmarks"))] mod preimage_provider { use frame_support::traits::PreimageProvider; pub trait PreimageProviderAndMaybeRecipient: PreimageProvider {} impl> PreimageProviderAndMaybeRecipient for T {} } pub use preimage_provider::PreimageProviderAndMaybeRecipient; pub(crate) trait MarginalWeightInfo: WeightInfo { fn item(periodic: bool, named: bool, resolved: Option) -> Weight { match (periodic, named, resolved) { (_, false, None) => Self::on_initialize_aborted(2) - Self::on_initialize_aborted(1), (_, true, None) => Self::on_initialize_named_aborted(2) - Self::on_initialize_named_aborted(1), (false, false, Some(false)) => Self::on_initialize(2) - Self::on_initialize(1), (false, true, Some(false)) => Self::on_initialize_named(2) - Self::on_initialize_named(1), (true, false, Some(false)) => Self::on_initialize_periodic(2) - Self::on_initialize_periodic(1), (true, true, Some(false)) => Self::on_initialize_periodic_named(2) - Self::on_initialize_periodic_named(1), (false, false, Some(true)) => Self::on_initialize_resolved(2) - Self::on_initialize_resolved(1), (false, true, Some(true)) => Self::on_initialize_named_resolved(2) - Self::on_initialize_named_resolved(1), (true, false, Some(true)) => Self::on_initialize_periodic_resolved(2) - Self::on_initialize_periodic_resolved(1), (true, true, Some(true)) => Self::on_initialize_periodic_named_resolved(2) - Self::on_initialize_periodic_named_resolved(1), } } } impl MarginalWeightInfo for T {} #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::{ dispatch::PostDispatchInfo, pallet_prelude::*, traits::{schedule::LookupError, PreimageProvider}, }; use frame_system::pallet_prelude::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); /// `system::Config` should always be included in our implied traits. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The aggregated origin which the dispatch will take. type RuntimeOrigin: OriginTrait + From + IsType<::RuntimeOrigin>; /// The caller origin, overarching type of all pallets origins. type PalletsOrigin: From> + Codec + Clone + Eq + TypeInfo; /// The aggregated call type. type RuntimeCall: Parameter + Dispatchable< RuntimeOrigin = ::RuntimeOrigin, PostInfo = PostDispatchInfo, > + GetDispatchInfo + From>; /// The maximum weight that may be scheduled per block for any dispatchables of less /// priority than `schedule::HARD_DEADLINE`. #[pallet::constant] type MaximumWeight: Get; /// Required origin to schedule or cancel calls. type ScheduleOrigin: EnsureOrigin<::RuntimeOrigin>; /// Compare the privileges of origins. /// /// This will be used when canceling a task, to ensure that the origin that tries /// to cancel has greater or equal privileges as the origin that created the scheduled task. /// /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can /// be used. This will only check if two given origins are equal. type OriginPrivilegeCmp: PrivilegeCmp; /// The maximum number of scheduled calls in the queue for a single block. /// Not strictly enforced, but used for weight estimation. #[pallet::constant] type MaxScheduledPerBlock: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The preimage provider with which we look up call hashes to get the call. type PreimageProvider: PreimageProviderAndMaybeRecipient; /// If `Some` then the number of blocks to postpone execution for when the item is delayed. type NoPreimagePostponement: Get>; } /// Items to be executed, indexed by the block number that they should be executed on. #[pallet::storage] pub type Agenda = StorageMap<_, Twox64Concat, T::BlockNumber, Vec>>, ValueQuery>; /// Lookup from identity to the block number and index of the task. #[pallet::storage] pub(crate) type Lookup = StorageMap<_, Twox64Concat, Vec, TaskAddress>; /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Scheduled some task. Scheduled { when: T::BlockNumber, index: u32 }, /// Canceled some task. Canceled { when: T::BlockNumber, index: u32 }, /// Dispatched some task. Dispatched { task: TaskAddress, id: Option>, result: DispatchResult, }, /// The call for the provided hash was not found so the task has been aborted. CallLookupFailed { task: TaskAddress, id: Option>, error: LookupError, }, } #[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 fn on_initialize(now: T::BlockNumber) -> Weight { let limit = T::MaximumWeight::get(); let mut queued = Agenda::::take(now) .into_iter() .enumerate() .filter_map(|(index, s)| Some((index as u32, s?))) .collect::>(); if queued.len() as u32 > T::MaxScheduledPerBlock::get() { log::warn!( target: "runtime::scheduler", "Warning: This block has more items queued in Scheduler than \ expected from the runtime configuration. An update might be needed." ); } queued.sort_by_key(|(_, s)| s.priority); let next = now + One::one(); let mut total_weight: Weight = T::WeightInfo::on_initialize(0); for (order, (index, mut s)) in queued.into_iter().enumerate() { let named = if let Some(ref id) = s.maybe_id { Lookup::::remove(id); true } else { false }; let (call, maybe_completed) = s.call.resolved::(); s.call = call; let resolved = if let Some(completed) = maybe_completed { T::PreimageProvider::unrequest_preimage(&completed); true } else { false }; let call = match s.call.as_value().cloned() { Some(c) => c, None => { // Preimage not available - postpone until some block. total_weight.saturating_accrue(T::WeightInfo::item(false, named, None)); if let Some(delay) = T::NoPreimagePostponement::get() { let until = now.saturating_add(delay); if let Some(ref id) = s.maybe_id { let index = Agenda::::decode_len(until).unwrap_or(0); Lookup::::insert(id, (until, index as u32)); } Agenda::::append(until, Some(s)); } continue }, }; let periodic = s.maybe_periodic.is_some(); let call_weight = call.get_dispatch_info().weight; let mut item_weight = T::WeightInfo::item(periodic, named, Some(resolved)); let origin = <::RuntimeOrigin as From>::from( s.origin.clone(), ) .into(); if ensure_signed(origin).is_ok() { // Weights of Signed dispatches expect their signing account to be whitelisted. item_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } // We allow a scheduled call if any is true: // - It's priority is `HARD_DEADLINE` // - It does not push the weight past the limit. // - It is the first item in the schedule let hard_deadline = s.priority <= schedule::HARD_DEADLINE; let test_weight = total_weight.saturating_add(call_weight).saturating_add(item_weight); if !hard_deadline && order > 0 && test_weight.any_gt(limit) { // Cannot be scheduled this block - postpone until next. total_weight.saturating_accrue(T::WeightInfo::item(false, named, None)); if let Some(ref id) = s.maybe_id { // NOTE: We could reasonably not do this (in which case there would be one // block where the named and delayed item could not be referenced by name), // but we will do it anyway since it should be mostly free in terms of // weight and it is slightly cleaner. let index = Agenda::::decode_len(next).unwrap_or(0); Lookup::::insert(id, (next, index as u32)); } Agenda::::append(next, Some(s)); continue } let dispatch_origin = s.origin.clone().into(); let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) { Ok(post_info) => (post_info.actual_weight, Ok(())), Err(error_and_info) => (error_and_info.post_info.actual_weight, Err(error_and_info.error)), }; let actual_call_weight = maybe_actual_call_weight.unwrap_or(call_weight); total_weight.saturating_accrue(item_weight); total_weight.saturating_accrue(actual_call_weight); Self::deposit_event(Event::Dispatched { task: (now, index), id: s.maybe_id.clone(), result, }); if let &Some((period, count)) = &s.maybe_periodic { if count > 1 { s.maybe_periodic = Some((period, count - 1)); } else { s.maybe_periodic = None; } let wake = now + period; // If scheduled is named, place its information in `Lookup` if let Some(ref id) = s.maybe_id { let wake_index = Agenda::::decode_len(wake).unwrap_or(0); Lookup::::insert(id, (wake, wake_index as u32)); } Agenda::::append(wake, Some(s)); } } total_weight } } #[pallet::call] impl Pallet { /// Anonymously schedule a task. #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule( origin: OriginFor, when: T::BlockNumber, maybe_periodic: Option>, priority: schedule::Priority, call: Box>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::RuntimeOrigin::from(origin); Self::do_schedule( DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call, )?; Ok(()) } /// Cancel an anonymously scheduled task. #[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 = ::RuntimeOrigin::from(origin); Self::do_cancel(Some(origin.caller().clone()), (when, index))?; Ok(()) } /// Schedule a named task. #[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>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::RuntimeOrigin::from(origin); Self::do_schedule_named( id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call, )?; Ok(()) } /// Cancel a named scheduled task. #[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 = ::RuntimeOrigin::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>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::RuntimeOrigin::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`](Self::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>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::RuntimeOrigin::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 V3. /// /// Returns the weight consumed by this migration. pub fn migrate_v1_to_v3() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); Agenda::::translate::< Vec::RuntimeCall, T::BlockNumber>>>, _, >(|_, agenda| { Some( agenda .into_iter() .map(|schedule| { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); schedule.map(|schedule| ScheduledV3 { maybe_id: schedule.maybe_id, priority: schedule.priority, call: schedule.call.into(), maybe_periodic: schedule.maybe_periodic, origin: system::RawOrigin::Root.into(), _phantom: Default::default(), }) }) .collect::>(), ) }); #[allow(deprecated)] frame_support::storage::migration::remove_storage_prefix( Self::name().as_bytes(), b"StorageVersion", &[], ); StorageVersion::new(3).put::(); weight + T::DbWeight::get().writes(2) } /// Migrate storage format from V2 to V3. /// /// Returns the weight consumed by this migration. pub fn migrate_v2_to_v3() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); Agenda::::translate::>>, _>(|_, agenda| { Some( agenda .into_iter() .map(|schedule| { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); schedule.map(|schedule| ScheduledV3 { maybe_id: schedule.maybe_id, priority: schedule.priority, call: schedule.call.into(), maybe_periodic: schedule.maybe_periodic, origin: schedule.origin, _phantom: Default::default(), }) }) .collect::>(), ) }); #[allow(deprecated)] frame_support::storage::migration::remove_storage_prefix( Self::name().as_bytes(), b"StorageVersion", &[], ); StorageVersion::new(3).put::(); weight + T::DbWeight::get().writes(2) } #[cfg(feature = "try-runtime")] pub fn pre_migrate_to_v3() -> Result<(), &'static str> { Ok(()) } #[cfg(feature = "try-runtime")] pub fn post_migrate_to_v3() -> Result<(), &'static str> { use frame_support::dispatch::GetStorageVersion; assert!(Self::current_storage_version() == 3); for k in Agenda::::iter_keys() { let _ = Agenda::::try_get(k).map_err(|()| "Invalid item in Agenda")?; } Ok(()) } /// Helper to migrate scheduler when the pallet origin type has changed. pub fn migrate_origin + codec::Decode>() { Agenda::::translate::< Vec, 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: CallOrHashOf, ) -> Result, DispatchError> { let when = Self::resolve_time(when)?; call.ensure_requested::(); // 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; 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 matches!( T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), Some(Ordering::Less) | None ) { return Err(BadOrigin.into()) } }; Ok(s.take()) }, ) })?; if let Some(s) = scheduled { s.call.ensure_unrequested::(); if let Some(id) = s.maybe_id { Lookup::::remove(id); } Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { return Err(Error::::NotFound.into()) } } 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 { when: new_time, index: 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: CallOrHashOf, ) -> Result, DispatchError> { // ensure id it is unique if Lookup::::contains_key(&id) { return Err(Error::::FailedToSchedule.into()) } let when = Self::resolve_time(when)?; call.ensure_requested::(); // 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; 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 matches!( T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), Some(Ordering::Less) | None ) { return Err(BadOrigin.into()) } s.call.ensure_unrequested::(); } *s = None; } Ok(()) })?; Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { return Err(Error::::NotFound.into()) } }) } 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 { when: new_time, index: new_index }); *lookup = Some((new_time, new_index)); Ok((new_time, new_index)) }, ) } } impl schedule::v2::Anon::RuntimeCall, T::PalletsOrigin> for Pallet { type Address = TaskAddress; type Hash = T::Hash; fn schedule( when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, call: CallOrHashOf, ) -> 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::v2::Named::RuntimeCall, T::PalletsOrigin> for Pallet { type Address = TaskAddress; type Hash = T::Hash; fn schedule_named( id: Vec, when: DispatchTime, maybe_periodic: Option>, priority: schedule::Priority, origin: T::PalletsOrigin, call: CallOrHashOf, ) -> 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(()) } }