1#![doc = docify::embed!("src/tests.rs", basic_scheduling_works)]
48#![doc = docify::embed!("src/tests.rs", scheduling_with_preimages_works)]
50
51#![cfg_attr(not(feature = "std"), no_std)]
76
77#[cfg(feature = "runtime-benchmarks")]
78mod benchmarking;
79pub mod migration;
80#[cfg(test)]
81mod mock;
82#[cfg(test)]
83mod tests;
84pub mod weights;
85
86extern crate alloc;
87
88use alloc::{boxed::Box, vec::Vec};
89use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
90use core::{borrow::Borrow, cmp::Ordering, marker::PhantomData};
91use frame_support::{
92 dispatch::{DispatchResult, GetDispatchInfo, Parameter, RawOrigin},
93 ensure,
94 traits::{
95 schedule::{self, DispatchTime, MaybeHashed},
96 Bounded, CallerTrait, EnsureOrigin, Get, IsType, OriginTrait, PalletInfoAccess,
97 PrivilegeCmp, QueryPreimage, StorageVersion, StorePreimage,
98 },
99 weights::{Weight, WeightMeter},
100};
101use frame_system::{self as system};
102use scale_info::TypeInfo;
103use sp_io::hashing::blake2_256;
104use sp_runtime::{
105 traits::{BadOrigin, BlockNumberProvider, Dispatchable, One, Saturating, Zero},
106 BoundedVec, Debug, DispatchError,
107};
108
109pub use pallet::*;
110pub use weights::WeightInfo;
111
112pub type PeriodicIndex = u32;
114pub type TaskAddress<BlockNumber> = (BlockNumber, u32);
116
117pub type CallOrHashOf<T> =
118 MaybeHashed<<T as Config>::RuntimeCall, <T as frame_system::Config>::Hash>;
119
120pub type BoundedCallOf<T> =
121 Bounded<<T as Config>::RuntimeCall, <T as frame_system::Config>::Hashing>;
122
123pub type BlockNumberFor<T> =
124 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
125
126#[derive(
128 Clone,
129 Copy,
130 Debug,
131 PartialEq,
132 Eq,
133 Encode,
134 Decode,
135 DecodeWithMemTracking,
136 MaxEncodedLen,
137 TypeInfo,
138)]
139pub struct RetryConfig<Period> {
140 pub total_retries: u8,
142 pub remaining: u8,
144 pub period: Period,
146}
147
148#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]
149#[derive(Clone, Debug, Encode, Decode)]
150struct ScheduledV1<Call, BlockNumber> {
151 maybe_id: Option<Vec<u8>>,
152 priority: schedule::Priority,
153 call: Call,
154 maybe_periodic: Option<schedule::Period<BlockNumber>>,
155}
156
157#[derive(
159 Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking,
160)]
161pub struct Scheduled<Name, Call, BlockNumber, PalletsOrigin, AccountId> {
162 pub maybe_id: Option<Name>,
164 pub priority: schedule::Priority,
166 pub call: Call,
168 pub maybe_periodic: Option<schedule::Period<BlockNumber>>,
170 pub origin: PalletsOrigin,
172 #[doc(hidden)]
173 pub _phantom: PhantomData<AccountId>,
174}
175
176impl<Name, Call, BlockNumber, PalletsOrigin, AccountId>
177 Scheduled<Name, Call, BlockNumber, PalletsOrigin, AccountId>
178where
179 Call: Clone,
180 PalletsOrigin: Clone,
181{
182 pub fn as_retry(&self) -> Self {
185 Self {
186 maybe_id: None,
187 priority: self.priority,
188 call: self.call.clone(),
189 maybe_periodic: None,
190 origin: self.origin.clone(),
191 _phantom: Default::default(),
192 }
193 }
194}
195
196use crate::{Scheduled as ScheduledV3, Scheduled as ScheduledV2};
197
198pub type ScheduledV2Of<T> = ScheduledV2<
199 Vec<u8>,
200 <T as Config>::RuntimeCall,
201 BlockNumberFor<T>,
202 <T as Config>::PalletsOrigin,
203 <T as frame_system::Config>::AccountId,
204>;
205
206pub type ScheduledV3Of<T> = ScheduledV3<
207 Vec<u8>,
208 CallOrHashOf<T>,
209 BlockNumberFor<T>,
210 <T as Config>::PalletsOrigin,
211 <T as frame_system::Config>::AccountId,
212>;
213
214pub type ScheduledOf<T> = Scheduled<
215 TaskName,
216 BoundedCallOf<T>,
217 BlockNumberFor<T>,
218 <T as Config>::PalletsOrigin,
219 <T as frame_system::Config>::AccountId,
220>;
221
222pub(crate) trait MarginalWeightInfo: WeightInfo {
223 fn service_task(maybe_lookup_len: Option<usize>, named: bool, periodic: bool) -> Weight {
224 let base = Self::service_task_base();
225 let mut total = match maybe_lookup_len {
226 None => base,
227 Some(l) => Self::service_task_fetched(l as u32),
228 };
229 if named {
230 total.saturating_accrue(Self::service_task_named().saturating_sub(base));
231 }
232 if periodic {
233 total.saturating_accrue(Self::service_task_periodic().saturating_sub(base));
234 }
235 total
236 }
237}
238impl<T: WeightInfo> MarginalWeightInfo for T {}
239
240#[frame_support::pallet]
241pub mod pallet {
242 use super::*;
243 use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*};
244 use frame_system::pallet_prelude::{BlockNumberFor as SystemBlockNumberFor, OriginFor};
245
246 const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
248
249 #[pallet::pallet]
250 #[pallet::storage_version(STORAGE_VERSION)]
251 pub struct Pallet<T>(_);
252
253 #[pallet::config]
255 pub trait Config: frame_system::Config {
256 #[allow(deprecated)]
258 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
259
260 type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
262 + From<Self::PalletsOrigin>
263 + IsType<<Self as system::Config>::RuntimeOrigin>;
264
265 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>>
267 + CallerTrait<Self::AccountId>
268 + MaxEncodedLen;
269
270 type RuntimeCall: Parameter
272 + Dispatchable<
273 RuntimeOrigin = <Self as Config>::RuntimeOrigin,
274 PostInfo = PostDispatchInfo,
275 > + GetDispatchInfo
276 + From<system::Call<Self>>;
277
278 #[pallet::constant]
280 type MaximumWeight: Get<Weight>;
281
282 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::RuntimeOrigin>;
284
285 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;
293
294 #[pallet::constant]
300 type MaxScheduledPerBlock: Get<u32>;
301
302 type WeightInfo: WeightInfo;
304
305 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
307
308 type BlockNumberProvider: BlockNumberProvider;
336 }
337
338 #[pallet::storage]
340 pub type IncompleteSince<T: Config> = StorageValue<_, BlockNumberFor<T>>;
341
342 #[pallet::storage]
344 pub type Agenda<T: Config> = StorageMap<
345 _,
346 Twox64Concat,
347 BlockNumberFor<T>,
348 BoundedVec<Option<ScheduledOf<T>>, T::MaxScheduledPerBlock>,
349 ValueQuery,
350 >;
351
352 #[pallet::storage]
354 pub type Retries<T: Config> = StorageMap<
355 _,
356 Blake2_128Concat,
357 TaskAddress<BlockNumberFor<T>>,
358 RetryConfig<BlockNumberFor<T>>,
359 OptionQuery,
360 >;
361
362 #[pallet::storage]
367 pub type Lookup<T: Config> =
368 StorageMap<_, Twox64Concat, TaskName, TaskAddress<BlockNumberFor<T>>>;
369
370 #[pallet::event]
372 #[pallet::generate_deposit(pub(super) fn deposit_event)]
373 pub enum Event<T: Config> {
374 Scheduled { when: BlockNumberFor<T>, index: u32 },
376 Canceled { when: BlockNumberFor<T>, index: u32 },
378 Dispatched {
380 task: TaskAddress<BlockNumberFor<T>>,
381 id: Option<TaskName>,
382 result: DispatchResult,
383 },
384 RetrySet {
386 task: TaskAddress<BlockNumberFor<T>>,
387 id: Option<TaskName>,
388 period: BlockNumberFor<T>,
389 retries: u8,
390 },
391 RetryCancelled { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
393 CallUnavailable { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
395 PeriodicFailed { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
397 RetryFailed { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
400 PermanentlyOverweight { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
402 AgendaIncomplete { when: BlockNumberFor<T> },
404 }
405
406 #[pallet::error]
407 pub enum Error<T> {
408 FailedToSchedule,
410 NotFound,
412 TargetBlockNumberInPast,
414 RescheduleNoChange,
416 Named,
418 }
419
420 #[pallet::hooks]
421 impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
422 fn on_initialize(_now: SystemBlockNumberFor<T>) -> Weight {
424 let now = T::BlockNumberProvider::current_block_number();
425 let mut weight_counter = frame_system::Pallet::<T>::remaining_block_weight()
426 .limit_to(T::MaximumWeight::get());
427 Self::service_agendas(&mut weight_counter, now, u32::MAX);
428 weight_counter.consumed()
429 }
430
431 #[cfg(feature = "std")]
432 fn integrity_test() {
433 fn lookup_weight<T: Config>(s: usize) -> Weight {
435 T::WeightInfo::service_agendas_base() +
436 T::WeightInfo::service_agenda_base(T::MaxScheduledPerBlock::get()) +
437 T::WeightInfo::service_task(Some(s), true, true)
438 }
439
440 let limit = sp_runtime::Perbill::from_percent(90) * T::MaximumWeight::get();
441
442 let small_lookup = lookup_weight::<T>(128);
443 assert!(small_lookup.all_lte(limit), "Must be possible to submit a small lookup");
444
445 let medium_lookup = lookup_weight::<T>(1024);
446 assert!(medium_lookup.all_lte(limit), "Must be possible to submit a medium lookup");
447
448 let large_lookup = lookup_weight::<T>(1024 * 1024);
449 assert!(large_lookup.all_lte(limit), "Must be possible to submit a large lookup");
450 }
451 }
452
453 #[pallet::call]
454 impl<T: Config> Pallet<T> {
455 #[pallet::call_index(0)]
457 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]
458 pub fn schedule(
459 origin: OriginFor<T>,
460 when: BlockNumberFor<T>,
461 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
462 priority: schedule::Priority,
463 call: Box<<T as Config>::RuntimeCall>,
464 ) -> DispatchResult {
465 T::ScheduleOrigin::ensure_origin(origin.clone())?;
466 let origin = <T as Config>::RuntimeOrigin::from(origin);
467 Self::do_schedule(
468 DispatchTime::At(when),
469 maybe_periodic,
470 priority,
471 origin.caller().clone(),
472 T::Preimages::bound(*call)?,
473 )?;
474 Ok(())
475 }
476
477 #[pallet::call_index(1)]
482 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]
483 pub fn cancel(origin: OriginFor<T>, when: BlockNumberFor<T>, index: u32) -> DispatchResult {
484 T::ScheduleOrigin::ensure_origin(origin.clone())?;
485 let origin = <T as Config>::RuntimeOrigin::from(origin);
486 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;
487 Ok(())
488 }
489
490 #[pallet::call_index(2)]
492 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
493 pub fn schedule_named(
494 origin: OriginFor<T>,
495 id: TaskName,
496 when: BlockNumberFor<T>,
497 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
498 priority: schedule::Priority,
499 call: Box<<T as Config>::RuntimeCall>,
500 ) -> DispatchResult {
501 T::ScheduleOrigin::ensure_origin(origin.clone())?;
502 let origin = <T as Config>::RuntimeOrigin::from(origin);
503 Self::do_schedule_named(
504 id,
505 DispatchTime::At(when),
506 maybe_periodic,
507 priority,
508 origin.caller().clone(),
509 T::Preimages::bound(*call)?,
510 )?;
511 Ok(())
512 }
513
514 #[pallet::call_index(3)]
516 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]
517 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {
518 T::ScheduleOrigin::ensure_origin(origin.clone())?;
519 let origin = <T as Config>::RuntimeOrigin::from(origin);
520 Self::do_cancel_named(Some(origin.caller().clone()), id)?;
521 Ok(())
522 }
523
524 #[pallet::call_index(4)]
526 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]
527 pub fn schedule_after(
528 origin: OriginFor<T>,
529 after: BlockNumberFor<T>,
530 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
531 priority: schedule::Priority,
532 call: Box<<T as Config>::RuntimeCall>,
533 ) -> DispatchResult {
534 T::ScheduleOrigin::ensure_origin(origin.clone())?;
535 let origin = <T as Config>::RuntimeOrigin::from(origin);
536 Self::do_schedule(
537 DispatchTime::After(after),
538 maybe_periodic,
539 priority,
540 origin.caller().clone(),
541 T::Preimages::bound(*call)?,
542 )?;
543 Ok(())
544 }
545
546 #[pallet::call_index(5)]
548 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
549 pub fn schedule_named_after(
550 origin: OriginFor<T>,
551 id: TaskName,
552 after: BlockNumberFor<T>,
553 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
554 priority: schedule::Priority,
555 call: Box<<T as Config>::RuntimeCall>,
556 ) -> DispatchResult {
557 T::ScheduleOrigin::ensure_origin(origin.clone())?;
558 let origin = <T as Config>::RuntimeOrigin::from(origin);
559 Self::do_schedule_named(
560 id,
561 DispatchTime::After(after),
562 maybe_periodic,
563 priority,
564 origin.caller().clone(),
565 T::Preimages::bound(*call)?,
566 )?;
567 Ok(())
568 }
569
570 #[pallet::call_index(6)]
586 #[pallet::weight(<T as Config>::WeightInfo::set_retry())]
587 pub fn set_retry(
588 origin: OriginFor<T>,
589 task: TaskAddress<BlockNumberFor<T>>,
590 retries: u8,
591 period: BlockNumberFor<T>,
592 ) -> DispatchResult {
593 T::ScheduleOrigin::ensure_origin(origin.clone())?;
594 let origin = <T as Config>::RuntimeOrigin::from(origin);
595 let (when, index) = task;
596 let agenda = Agenda::<T>::get(when);
597 let scheduled = agenda
598 .get(index as usize)
599 .and_then(Option::as_ref)
600 .ok_or(Error::<T>::NotFound)?;
601 Self::ensure_privilege(origin.caller(), &scheduled.origin)?;
602 ensure!(scheduled.maybe_id.is_none(), Error::<T>::Named);
604 Retries::<T>::insert(
605 (when, index),
606 RetryConfig { total_retries: retries, remaining: retries, period },
607 );
608 Self::deposit_event(Event::RetrySet { task, id: None, period, retries });
609 Ok(())
610 }
611
612 #[pallet::call_index(7)]
627 #[pallet::weight(<T as Config>::WeightInfo::set_retry_named())]
628 pub fn set_retry_named(
629 origin: OriginFor<T>,
630 id: TaskName,
631 retries: u8,
632 period: BlockNumberFor<T>,
633 ) -> DispatchResult {
634 T::ScheduleOrigin::ensure_origin(origin.clone())?;
635 let origin = <T as Config>::RuntimeOrigin::from(origin);
636 let (when, agenda_index) = Lookup::<T>::get(&id).ok_or(Error::<T>::NotFound)?;
637 let agenda = Agenda::<T>::get(when);
638 let scheduled = agenda
639 .get(agenda_index as usize)
640 .and_then(Option::as_ref)
641 .ok_or(Error::<T>::NotFound)?;
642 Self::ensure_privilege(origin.caller(), &scheduled.origin)?;
643 Retries::<T>::insert(
644 (when, agenda_index),
645 RetryConfig { total_retries: retries, remaining: retries, period },
646 );
647 Self::deposit_event(Event::RetrySet {
648 task: (when, agenda_index),
649 id: Some(id),
650 period,
651 retries,
652 });
653 Ok(())
654 }
655
656 #[pallet::call_index(8)]
662 #[pallet::weight(<T as Config>::WeightInfo::cancel_retry())]
663 pub fn cancel_retry(
664 origin: OriginFor<T>,
665 task: TaskAddress<BlockNumberFor<T>>,
666 ) -> DispatchResult {
667 T::ScheduleOrigin::ensure_origin(origin.clone())?;
668 let origin = <T as Config>::RuntimeOrigin::from(origin);
669 Self::do_cancel_retry(origin.caller(), task)?;
670 Self::deposit_event(Event::RetryCancelled { task, id: None });
671 Ok(())
672 }
673
674 #[pallet::call_index(9)]
676 #[pallet::weight(<T as Config>::WeightInfo::cancel_retry_named())]
677 pub fn cancel_retry_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {
678 T::ScheduleOrigin::ensure_origin(origin.clone())?;
679 let origin = <T as Config>::RuntimeOrigin::from(origin);
680 let task = Lookup::<T>::get(&id).ok_or(Error::<T>::NotFound)?;
681 Self::do_cancel_retry(origin.caller(), task)?;
682 Self::deposit_event(Event::RetryCancelled { task, id: Some(id) });
683 Ok(())
684 }
685 }
686}
687
688impl<T: Config> Pallet<T> {
689 pub fn migrate_v1_to_v4() -> Weight {
693 use migration::v1 as old;
694 let mut weight = T::DbWeight::get().reads_writes(1, 1);
695
696 let keys = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
699 for key in keys {
700 weight.saturating_accrue(T::DbWeight::get().reads(1));
701 if let Err(_) = old::Agenda::<T>::try_get(&key) {
702 weight.saturating_accrue(T::DbWeight::get().writes(1));
703 old::Agenda::<T>::remove(&key);
704 log::warn!("Deleted undecodable agenda");
705 }
706 }
707
708 Agenda::<T>::translate::<
709 Vec<Option<ScheduledV1<<T as Config>::RuntimeCall, BlockNumberFor<T>>>>,
710 _,
711 >(|_, agenda| {
712 Some(BoundedVec::truncate_from(
713 agenda
714 .into_iter()
715 .map(|schedule| {
716 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
717
718 schedule.and_then(|schedule| {
719 if let Some(id) = schedule.maybe_id.as_ref() {
720 let name = blake2_256(id);
721 if let Some(item) = old::Lookup::<T>::take(id) {
722 Lookup::<T>::insert(name, item);
723 }
724 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
725 }
726
727 let call = T::Preimages::bound(schedule.call).ok()?;
728
729 if call.lookup_needed() {
730 weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
731 }
732
733 Some(Scheduled {
734 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
735 priority: schedule.priority,
736 call,
737 maybe_periodic: schedule.maybe_periodic,
738 origin: system::RawOrigin::Root.into(),
739 _phantom: Default::default(),
740 })
741 })
742 })
743 .collect::<Vec<_>>(),
744 ))
745 });
746
747 let _ = frame_support::storage::migration::clear_storage_prefix(
748 Self::name().as_bytes(),
749 b"StorageVersion",
750 &[],
751 None,
752 None,
753 );
754
755 StorageVersion::new(4).put::<Self>();
756
757 weight + T::DbWeight::get().writes(2)
758 }
759
760 pub fn migrate_v2_to_v4() -> Weight {
764 use migration::v2 as old;
765 let mut weight = T::DbWeight::get().reads_writes(1, 1);
766
767 let keys = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
770 for key in keys {
771 weight.saturating_accrue(T::DbWeight::get().reads(1));
772 if let Err(_) = old::Agenda::<T>::try_get(&key) {
773 weight.saturating_accrue(T::DbWeight::get().writes(1));
774 old::Agenda::<T>::remove(&key);
775 log::warn!("Deleted undecodable agenda");
776 }
777 }
778
779 Agenda::<T>::translate::<Vec<Option<ScheduledV2Of<T>>>, _>(|_, agenda| {
780 Some(BoundedVec::truncate_from(
781 agenda
782 .into_iter()
783 .map(|schedule| {
784 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
785 schedule.and_then(|schedule| {
786 if let Some(id) = schedule.maybe_id.as_ref() {
787 let name = blake2_256(id);
788 if let Some(item) = old::Lookup::<T>::take(id) {
789 Lookup::<T>::insert(name, item);
790 }
791 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
792 }
793
794 let call = T::Preimages::bound(schedule.call).ok()?;
795 if call.lookup_needed() {
796 weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
797 }
798
799 Some(Scheduled {
800 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
801 priority: schedule.priority,
802 call,
803 maybe_periodic: schedule.maybe_periodic,
804 origin: schedule.origin,
805 _phantom: Default::default(),
806 })
807 })
808 })
809 .collect::<Vec<_>>(),
810 ))
811 });
812
813 let _ = frame_support::storage::migration::clear_storage_prefix(
814 Self::name().as_bytes(),
815 b"StorageVersion",
816 &[],
817 None,
818 None,
819 );
820
821 StorageVersion::new(4).put::<Self>();
822
823 weight + T::DbWeight::get().writes(2)
824 }
825
826 #[allow(deprecated)]
830 pub fn migrate_v3_to_v4() -> Weight {
831 use migration::v3 as old;
832 let mut weight = T::DbWeight::get().reads_writes(2, 1);
833
834 let blocks = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
837 for block in blocks {
838 weight.saturating_accrue(T::DbWeight::get().reads(1));
839 if let Err(_) = old::Agenda::<T>::try_get(&block) {
840 weight.saturating_accrue(T::DbWeight::get().writes(1));
841 old::Agenda::<T>::remove(&block);
842 log::warn!("Deleted undecodable agenda of block: {:?}", block);
843 }
844 }
845
846 Agenda::<T>::translate::<Vec<Option<ScheduledV3Of<T>>>, _>(|block, agenda| {
847 log::info!("Migrating agenda of block: {:?}", &block);
848 Some(BoundedVec::truncate_from(
849 agenda
850 .into_iter()
851 .map(|schedule| {
852 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
853 schedule
854 .and_then(|schedule| {
855 if let Some(id) = schedule.maybe_id.as_ref() {
856 let name = blake2_256(id);
857 if let Some(item) = old::Lookup::<T>::take(id) {
858 Lookup::<T>::insert(name, item);
859 log::info!("Migrated name for id: {:?}", id);
860 } else {
861 log::error!("No name in Lookup for id: {:?}", &id);
862 }
863 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
864 } else {
865 log::info!("Schedule is unnamed");
866 }
867
868 let call = match schedule.call {
869 MaybeHashed::Hash(h) => {
870 let bounded = Bounded::from_legacy_hash(h);
871 if let Err(err) = T::Preimages::peek::<
873 <T as Config>::RuntimeCall,
874 >(&bounded)
875 {
876 log::error!(
877 "Dropping undecodable call {:?}: {:?}",
878 &h,
879 &err
880 );
881 return None;
882 }
883 weight.saturating_accrue(T::DbWeight::get().reads(1));
884 log::info!("Migrated call by hash, hash: {:?}", h);
885 bounded
886 },
887 MaybeHashed::Value(v) => {
888 let call = T::Preimages::bound(v)
889 .map_err(|e| {
890 log::error!("Could not bound Call: {:?}", e)
891 })
892 .ok()?;
893 if call.lookup_needed() {
894 weight.saturating_accrue(
895 T::DbWeight::get().reads_writes(0, 1),
896 );
897 }
898 log::info!(
899 "Migrated call by value, hash: {:?}",
900 call.hash()
901 );
902 call
903 },
904 };
905
906 Some(Scheduled {
907 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
908 priority: schedule.priority,
909 call,
910 maybe_periodic: schedule.maybe_periodic,
911 origin: schedule.origin,
912 _phantom: Default::default(),
913 })
914 })
915 .or_else(|| {
916 log::info!("Schedule in agenda for block {:?} is empty - nothing to do here.", &block);
917 None
918 })
919 })
920 .collect::<Vec<_>>(),
921 ))
922 });
923
924 let _ = frame_support::storage::migration::clear_storage_prefix(
925 Self::name().as_bytes(),
926 b"StorageVersion",
927 &[],
928 None,
929 None,
930 );
931
932 StorageVersion::new(4).put::<Self>();
933
934 weight + T::DbWeight::get().writes(2)
935 }
936}
937
938impl<T: Config> Pallet<T> {
939 pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {
941 Agenda::<T>::translate::<
942 Vec<
943 Option<
944 Scheduled<
945 TaskName,
946 BoundedCallOf<T>,
947 BlockNumberFor<T>,
948 OldOrigin,
949 T::AccountId,
950 >,
951 >,
952 >,
953 _,
954 >(|_, agenda| {
955 Some(BoundedVec::truncate_from(
956 agenda
957 .into_iter()
958 .map(|schedule| {
959 schedule.map(|schedule| Scheduled {
960 maybe_id: schedule.maybe_id,
961 priority: schedule.priority,
962 call: schedule.call,
963 maybe_periodic: schedule.maybe_periodic,
964 origin: schedule.origin.into(),
965 _phantom: Default::default(),
966 })
967 })
968 .collect::<Vec<_>>(),
969 ))
970 });
971 }
972
973 fn resolve_time(
974 when: DispatchTime<BlockNumberFor<T>>,
975 ) -> Result<BlockNumberFor<T>, DispatchError> {
976 let now = T::BlockNumberProvider::current_block_number();
977 let when = match when {
978 DispatchTime::At(x) => x,
979 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),
982 };
983
984 if when <= now {
985 return Err(Error::<T>::TargetBlockNumberInPast.into());
986 }
987
988 Ok(when)
989 }
990
991 fn place_task(
992 when: BlockNumberFor<T>,
993 what: ScheduledOf<T>,
994 ) -> Result<TaskAddress<BlockNumberFor<T>>, (DispatchError, ScheduledOf<T>)> {
995 let maybe_name = what.maybe_id;
996 let index = Self::push_to_agenda(when, what)?;
997 let address = (when, index);
998 if let Some(name) = maybe_name {
999 Lookup::<T>::insert(name, address)
1000 }
1001 Self::deposit_event(Event::Scheduled { when: address.0, index: address.1 });
1002 Ok(address)
1003 }
1004
1005 fn push_to_agenda(
1006 when: BlockNumberFor<T>,
1007 what: ScheduledOf<T>,
1008 ) -> Result<u32, (DispatchError, ScheduledOf<T>)> {
1009 let mut agenda = Agenda::<T>::get(when);
1010 let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() {
1011 let _ = agenda.try_push(Some(what));
1013 agenda.len() as u32 - 1
1014 } else {
1015 if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) {
1016 agenda[hole_index] = Some(what);
1017 hole_index as u32
1018 } else {
1019 return Err((DispatchError::Exhausted, what));
1020 }
1021 };
1022 Agenda::<T>::insert(when, agenda);
1023 Ok(index)
1024 }
1025
1026 fn cleanup_agenda(when: BlockNumberFor<T>) {
1029 let mut agenda = Agenda::<T>::get(when);
1030 match agenda.iter().rposition(|i| i.is_some()) {
1031 Some(i) if agenda.len() > i + 1 => {
1034 agenda.truncate(i + 1);
1035 Agenda::<T>::insert(when, agenda);
1036 },
1037 Some(_) => {},
1040 None => {
1042 Agenda::<T>::remove(when);
1043 },
1044 }
1045 }
1046
1047 fn do_schedule(
1048 when: DispatchTime<BlockNumberFor<T>>,
1049 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1050 priority: schedule::Priority,
1051 origin: T::PalletsOrigin,
1052 call: BoundedCallOf<T>,
1053 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1054 let when = Self::resolve_time(when)?;
1055
1056 let lookup_hash = call.lookup_hash();
1057
1058 let maybe_periodic = maybe_periodic
1060 .filter(|p| p.1 > 1 && !p.0.is_zero())
1061 .map(|(p, c)| (p, c - 1));
1063 let task = Scheduled {
1064 maybe_id: None,
1065 priority,
1066 call,
1067 maybe_periodic,
1068 origin,
1069 _phantom: PhantomData,
1070 };
1071 let res = Self::place_task(when, task).map_err(|x| x.0)?;
1072
1073 if let Some(hash) = lookup_hash {
1074 T::Preimages::request(&hash);
1076 }
1077
1078 Ok(res)
1079 }
1080
1081 fn do_cancel(
1082 origin: Option<T::PalletsOrigin>,
1083 (when, index): TaskAddress<BlockNumberFor<T>>,
1084 ) -> Result<(), DispatchError> {
1085 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {
1086 agenda.get_mut(index as usize).map_or(
1087 Ok(None),
1088 |s| -> Result<Option<Scheduled<_, _, _, _, _>>, DispatchError> {
1089 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
1090 Self::ensure_privilege(o, &s.origin)?;
1091 };
1092 Ok(s.take())
1093 },
1094 )
1095 })?;
1096 if let Some(s) = scheduled {
1097 T::Preimages::drop(&s.call);
1098 if let Some(id) = s.maybe_id {
1099 Lookup::<T>::remove(id);
1100 }
1101 Retries::<T>::remove((when, index));
1102 Self::cleanup_agenda(when);
1103 Self::deposit_event(Event::Canceled { when, index });
1104 Ok(())
1105 } else {
1106 return Err(Error::<T>::NotFound.into());
1107 }
1108 }
1109
1110 fn do_reschedule(
1111 (when, index): TaskAddress<BlockNumberFor<T>>,
1112 new_time: DispatchTime<BlockNumberFor<T>>,
1113 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1114 let new_time = Self::resolve_time(new_time)?;
1115
1116 if new_time == when {
1117 return Err(Error::<T>::RescheduleNoChange.into());
1118 }
1119
1120 let task = Agenda::<T>::try_mutate(when, |agenda| {
1121 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
1122 ensure!(!matches!(task, Some(Scheduled { maybe_id: Some(_), .. })), Error::<T>::Named);
1123 task.take().ok_or(Error::<T>::NotFound)
1124 })?;
1125 Self::cleanup_agenda(when);
1126 Self::deposit_event(Event::Canceled { when, index });
1127
1128 Self::place_task(new_time, task).map_err(|x| x.0)
1129 }
1130
1131 fn do_schedule_named(
1132 id: TaskName,
1133 when: DispatchTime<BlockNumberFor<T>>,
1134 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1135 priority: schedule::Priority,
1136 origin: T::PalletsOrigin,
1137 call: BoundedCallOf<T>,
1138 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1139 if Lookup::<T>::contains_key(&id) {
1141 return Err(Error::<T>::FailedToSchedule.into());
1142 }
1143
1144 let when = Self::resolve_time(when)?;
1145
1146 let lookup_hash = call.lookup_hash();
1147
1148 let maybe_periodic = maybe_periodic
1150 .filter(|p| p.1 > 1 && !p.0.is_zero())
1151 .map(|(p, c)| (p, c - 1));
1153
1154 let task = Scheduled {
1155 maybe_id: Some(id),
1156 priority,
1157 call,
1158 maybe_periodic,
1159 origin,
1160 _phantom: Default::default(),
1161 };
1162 let res = Self::place_task(when, task).map_err(|x| x.0)?;
1163
1164 if let Some(hash) = lookup_hash {
1165 T::Preimages::request(&hash);
1167 }
1168
1169 Ok(res)
1170 }
1171
1172 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: TaskName) -> DispatchResult {
1173 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {
1174 if let Some((when, index)) = lookup.take() {
1175 let i = index as usize;
1176 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
1177 if let Some(s) = agenda.get_mut(i) {
1178 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
1179 Self::ensure_privilege(o, &s.origin)?;
1180 Retries::<T>::remove((when, index));
1181 T::Preimages::drop(&s.call);
1182 }
1183 *s = None;
1184 }
1185 Ok(())
1186 })?;
1187 Self::cleanup_agenda(when);
1188 Self::deposit_event(Event::Canceled { when, index });
1189 Ok(())
1190 } else {
1191 return Err(Error::<T>::NotFound.into());
1192 }
1193 })
1194 }
1195
1196 fn do_reschedule_named(
1197 id: TaskName,
1198 new_time: DispatchTime<BlockNumberFor<T>>,
1199 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1200 let new_time = Self::resolve_time(new_time)?;
1201
1202 let lookup = Lookup::<T>::get(id);
1203 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;
1204
1205 if new_time == when {
1206 return Err(Error::<T>::RescheduleNoChange.into());
1207 }
1208
1209 let task = Agenda::<T>::try_mutate(when, |agenda| {
1210 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
1211 task.take().ok_or(Error::<T>::NotFound)
1212 })?;
1213 Self::cleanup_agenda(when);
1214 Self::deposit_event(Event::Canceled { when, index });
1215 Self::place_task(new_time, task).map_err(|x| x.0)
1216 }
1217
1218 fn do_cancel_retry(
1219 origin: &T::PalletsOrigin,
1220 (when, index): TaskAddress<BlockNumberFor<T>>,
1221 ) -> Result<(), DispatchError> {
1222 let agenda = Agenda::<T>::get(when);
1223 let scheduled = agenda
1224 .get(index as usize)
1225 .and_then(Option::as_ref)
1226 .ok_or(Error::<T>::NotFound)?;
1227 Self::ensure_privilege(origin, &scheduled.origin)?;
1228 Retries::<T>::remove((when, index));
1229 Ok(())
1230 }
1231}
1232
1233enum ServiceTaskError {
1234 Unavailable,
1236 Overweight,
1238}
1239use ServiceTaskError::*;
1240
1241impl<T: Config> Pallet<T> {
1242 fn service_agendas(weight: &mut WeightMeter, now: BlockNumberFor<T>, max: u32) {
1244 if weight.try_consume(T::WeightInfo::service_agendas_base()).is_err() {
1245 return;
1246 }
1247
1248 let mut incomplete_since = now + One::one();
1249 let mut when = IncompleteSince::<T>::take().unwrap_or(now);
1250 let mut is_first = true; let max_items = T::MaxScheduledPerBlock::get();
1253 let mut count_down = max;
1254 let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items);
1255 while count_down > 0 && when <= now && weight.can_consume(service_agenda_base_weight) {
1256 if !Self::service_agenda(weight, is_first, now, when, u32::MAX) {
1257 incomplete_since = incomplete_since.min(when);
1258 }
1259 is_first = false;
1260 when.saturating_inc();
1261 count_down.saturating_dec();
1262 }
1263 incomplete_since = incomplete_since.min(when);
1264 if incomplete_since <= now {
1265 Self::deposit_event(Event::AgendaIncomplete { when: incomplete_since });
1266 IncompleteSince::<T>::put(incomplete_since);
1267 } else {
1268 IncompleteSince::<T>::put(now + One::one());
1273 }
1274 }
1275
1276 fn service_agenda(
1279 weight: &mut WeightMeter,
1280 mut is_first: bool,
1281 now: BlockNumberFor<T>,
1282 when: BlockNumberFor<T>,
1283 max: u32,
1284 ) -> bool {
1285 let mut agenda = Agenda::<T>::get(when);
1286 let mut ordered = agenda
1287 .iter()
1288 .enumerate()
1289 .filter_map(|(index, maybe_item)| {
1290 maybe_item.as_ref().map(|item| (index as u32, item.priority))
1291 })
1292 .collect::<Vec<_>>();
1293 ordered.sort_by_key(|k| k.1);
1294 let within_limit = weight
1295 .try_consume(T::WeightInfo::service_agenda_base(ordered.len() as u32))
1296 .is_ok();
1297 debug_assert!(within_limit, "weight limit should have been checked in advance");
1298
1299 let mut postponed = (ordered.len() as u32).saturating_sub(max);
1301 let mut dropped = 0;
1303
1304 for (agenda_index, _) in ordered.into_iter().take(max as usize) {
1305 let Some(task) = agenda[agenda_index as usize].take() else { continue };
1306 let base_weight = T::WeightInfo::service_task(
1307 task.call.lookup_len().map(|x| x as usize),
1308 task.maybe_id.is_some(),
1309 task.maybe_periodic.is_some(),
1310 );
1311 if !weight.can_consume(base_weight) {
1312 postponed += 1;
1313 agenda[agenda_index as usize] = Some(task);
1314 break;
1315 }
1316 let result = Self::service_task(weight, now, when, agenda_index, is_first, task);
1317 agenda[agenda_index as usize] = match result {
1318 Err((Unavailable, slot)) => {
1319 dropped += 1;
1320 slot
1321 },
1322 Err((Overweight, slot)) => {
1323 postponed += 1;
1324 slot
1325 },
1326 Ok(()) => {
1327 is_first = false;
1328 None
1329 },
1330 };
1331 }
1332 if postponed > 0 || dropped > 0 {
1333 Agenda::<T>::insert(when, agenda);
1334 } else {
1335 Agenda::<T>::remove(when);
1336 }
1337
1338 postponed == 0
1339 }
1340
1341 fn service_task(
1348 weight: &mut WeightMeter,
1349 now: BlockNumberFor<T>,
1350 when: BlockNumberFor<T>,
1351 agenda_index: u32,
1352 is_first: bool,
1353 mut task: ScheduledOf<T>,
1354 ) -> Result<(), (ServiceTaskError, Option<ScheduledOf<T>>)> {
1355 if let Some(ref id) = task.maybe_id {
1356 Lookup::<T>::remove(id);
1357 }
1358
1359 let (call, lookup_len) = match T::Preimages::peek(&task.call) {
1360 Ok(c) => c,
1361 Err(_) => {
1362 Self::deposit_event(Event::CallUnavailable {
1363 task: (when, agenda_index),
1364 id: task.maybe_id,
1365 });
1366
1367 T::Preimages::drop(&task.call);
1370
1371 let _ = weight.try_consume(T::WeightInfo::service_task(
1373 task.call.lookup_len().map(|x| x as usize),
1374 task.maybe_id.is_some(),
1375 task.maybe_periodic.is_some(),
1376 ));
1377
1378 return Err((Unavailable, Some(task)));
1379 },
1380 };
1381
1382 let _ = weight.try_consume(T::WeightInfo::service_task(
1383 lookup_len.map(|x| x as usize),
1384 task.maybe_id.is_some(),
1385 task.maybe_periodic.is_some(),
1386 ));
1387
1388 match Self::execute_dispatch(weight, task.origin.clone(), call) {
1389 Err(()) if is_first => {
1390 T::Preimages::drop(&task.call);
1391 Self::deposit_event(Event::PermanentlyOverweight {
1392 task: (when, agenda_index),
1393 id: task.maybe_id,
1394 });
1395 Err((Unavailable, Some(task)))
1396 },
1397 Err(()) => Err((Overweight, Some(task))),
1398 Ok(result) => {
1399 let failed = result.is_err();
1400 let maybe_retry_config = Retries::<T>::take((when, agenda_index));
1401 Self::deposit_event(Event::Dispatched {
1402 task: (when, agenda_index),
1403 id: task.maybe_id,
1404 result,
1405 });
1406
1407 match maybe_retry_config {
1408 Some(retry_config) if failed => {
1409 Self::schedule_retry(weight, now, when, agenda_index, &task, retry_config);
1410 },
1411 _ => {},
1412 }
1413
1414 if let &Some((period, count)) = &task.maybe_periodic {
1415 if count > 1 {
1416 task.maybe_periodic = Some((period, count - 1));
1417 } else {
1418 task.maybe_periodic = None;
1419 }
1420 let wake = now.saturating_add(period);
1421 match Self::place_task(wake, task) {
1422 Ok(new_address) => {
1423 if let Some(retry_config) = maybe_retry_config {
1424 Retries::<T>::insert(new_address, retry_config);
1425 }
1426 },
1427 Err((_, task)) => {
1428 T::Preimages::drop(&task.call);
1431 Self::deposit_event(Event::PeriodicFailed {
1432 task: (when, agenda_index),
1433 id: task.maybe_id,
1434 });
1435 },
1436 }
1437 } else {
1438 T::Preimages::drop(&task.call);
1439 }
1440 Ok(())
1441 },
1442 }
1443 }
1444
1445 fn execute_dispatch(
1454 weight: &mut WeightMeter,
1455 origin: T::PalletsOrigin,
1456 call: <T as Config>::RuntimeCall,
1457 ) -> Result<DispatchResult, ()> {
1458 let base_weight = match origin.as_system_ref() {
1459 Some(&RawOrigin::Signed(_)) => T::WeightInfo::execute_dispatch_signed(),
1460 _ => T::WeightInfo::execute_dispatch_unsigned(),
1461 };
1462 let call_weight = call.get_dispatch_info().call_weight;
1463 let max_weight = base_weight.saturating_add(call_weight);
1465
1466 if !weight.can_consume(max_weight) {
1467 return Err(());
1468 }
1469
1470 let dispatch_origin = origin.into();
1471 let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) {
1472 Ok(post_info) => (post_info.actual_weight, Ok(())),
1473 Err(error_and_info) => {
1474 (error_and_info.post_info.actual_weight, Err(error_and_info.error))
1475 },
1476 };
1477 let call_weight = maybe_actual_call_weight.unwrap_or(call_weight);
1478 let _ = weight.try_consume(base_weight);
1479 let _ = weight.try_consume(call_weight);
1480 Ok(result)
1481 }
1482
1483 fn schedule_retry(
1491 weight: &mut WeightMeter,
1492 now: BlockNumberFor<T>,
1493 when: BlockNumberFor<T>,
1494 agenda_index: u32,
1495 task: &ScheduledOf<T>,
1496 retry_config: RetryConfig<BlockNumberFor<T>>,
1497 ) {
1498 if weight
1499 .try_consume(T::WeightInfo::schedule_retry(T::MaxScheduledPerBlock::get()))
1500 .is_err()
1501 {
1502 Self::deposit_event(Event::RetryFailed {
1503 task: (when, agenda_index),
1504 id: task.maybe_id,
1505 });
1506 return;
1507 }
1508
1509 let RetryConfig { total_retries, mut remaining, period } = retry_config;
1510 remaining = match remaining.checked_sub(1) {
1511 Some(n) => n,
1512 None => return,
1513 };
1514 let wake = now.saturating_add(period);
1515 match Self::place_task(wake, task.as_retry()) {
1516 Ok(address) => {
1517 Retries::<T>::insert(address, RetryConfig { total_retries, remaining, period });
1520 },
1521 Err((_, task)) => {
1522 T::Preimages::drop(&task.call);
1525 Self::deposit_event(Event::RetryFailed {
1526 task: (when, agenda_index),
1527 id: task.maybe_id,
1528 });
1529 },
1530 }
1531 }
1532
1533 fn ensure_privilege(
1537 left: &<T as Config>::PalletsOrigin,
1538 right: &<T as Config>::PalletsOrigin,
1539 ) -> Result<(), DispatchError> {
1540 if matches!(T::OriginPrivilegeCmp::cmp_privilege(left, right), Some(Ordering::Less) | None)
1541 {
1542 return Err(BadOrigin.into());
1543 }
1544 Ok(())
1545 }
1546}
1547
1548impl<T: Config> schedule::v3::Anon<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1549 for Pallet<T>
1550{
1551 type Address = TaskAddress<BlockNumberFor<T>>;
1552 type Hasher = T::Hashing;
1553
1554 fn schedule(
1555 when: DispatchTime<BlockNumberFor<T>>,
1556 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1557 priority: schedule::Priority,
1558 origin: T::PalletsOrigin,
1559 call: BoundedCallOf<T>,
1560 ) -> Result<Self::Address, DispatchError> {
1561 Self::do_schedule(when, maybe_periodic, priority, origin, call)
1562 }
1563
1564 fn cancel((when, index): Self::Address) -> Result<(), DispatchError> {
1565 Self::do_cancel(None, (when, index)).map_err(map_err_to_v3_err::<T>)
1566 }
1567
1568 fn reschedule(
1569 address: Self::Address,
1570 when: DispatchTime<BlockNumberFor<T>>,
1571 ) -> Result<Self::Address, DispatchError> {
1572 Self::do_reschedule(address, when).map_err(map_err_to_v3_err::<T>)
1573 }
1574
1575 fn next_dispatch_time(
1576 (when, index): Self::Address,
1577 ) -> Result<BlockNumberFor<T>, DispatchError> {
1578 Agenda::<T>::get(when)
1579 .get(index as usize)
1580 .ok_or(DispatchError::Unavailable)
1581 .map(|_| when)
1582 }
1583}
1584
1585use schedule::v3::TaskName;
1586
1587impl<T: Config> schedule::v3::Named<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1588 for Pallet<T>
1589{
1590 type Address = TaskAddress<BlockNumberFor<T>>;
1591 type Hasher = T::Hashing;
1592
1593 fn schedule_named(
1594 id: TaskName,
1595 when: DispatchTime<BlockNumberFor<T>>,
1596 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1597 priority: schedule::Priority,
1598 origin: T::PalletsOrigin,
1599 call: BoundedCallOf<T>,
1600 ) -> Result<Self::Address, DispatchError> {
1601 Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call)
1602 }
1603
1604 fn cancel_named(id: TaskName) -> Result<(), DispatchError> {
1605 Self::do_cancel_named(None, id).map_err(map_err_to_v3_err::<T>)
1606 }
1607
1608 fn reschedule_named(
1609 id: TaskName,
1610 when: DispatchTime<BlockNumberFor<T>>,
1611 ) -> Result<Self::Address, DispatchError> {
1612 Self::do_reschedule_named(id, when).map_err(map_err_to_v3_err::<T>)
1613 }
1614
1615 fn next_dispatch_time(id: TaskName) -> Result<BlockNumberFor<T>, DispatchError> {
1616 Lookup::<T>::get(id)
1617 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))
1618 .ok_or(DispatchError::Unavailable)
1619 }
1620}
1621
1622fn map_err_to_v3_err<T: Config>(err: DispatchError) -> DispatchError {
1624 if err == DispatchError::from(Error::<T>::NotFound) {
1625 DispatchError::Unavailable
1626 } else {
1627 err
1628 }
1629}