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