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 total_retries: u8,
144 remaining: u8,
146 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(Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
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 = WeightMeter::with_limit(T::MaximumWeight::get());
426 Self::service_agendas(&mut weight_counter, now, u32::MAX);
427 weight_counter.consumed()
428 }
429
430 #[cfg(feature = "std")]
431 fn integrity_test() {
432 fn lookup_weight<T: Config>(s: usize) -> Weight {
434 T::WeightInfo::service_agendas_base() +
435 T::WeightInfo::service_agenda_base(T::MaxScheduledPerBlock::get()) +
436 T::WeightInfo::service_task(Some(s), true, true)
437 }
438
439 let limit = sp_runtime::Perbill::from_percent(90) * T::MaximumWeight::get();
440
441 let small_lookup = lookup_weight::<T>(128);
442 assert!(small_lookup.all_lte(limit), "Must be possible to submit a small lookup");
443
444 let medium_lookup = lookup_weight::<T>(1024);
445 assert!(medium_lookup.all_lte(limit), "Must be possible to submit a medium lookup");
446
447 let large_lookup = lookup_weight::<T>(1024 * 1024);
448 assert!(large_lookup.all_lte(limit), "Must be possible to submit a large lookup");
449 }
450 }
451
452 #[pallet::call]
453 impl<T: Config> Pallet<T> {
454 #[pallet::call_index(0)]
456 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]
457 pub fn schedule(
458 origin: OriginFor<T>,
459 when: BlockNumberFor<T>,
460 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
461 priority: schedule::Priority,
462 call: Box<<T as Config>::RuntimeCall>,
463 ) -> DispatchResult {
464 T::ScheduleOrigin::ensure_origin(origin.clone())?;
465 let origin = <T as Config>::RuntimeOrigin::from(origin);
466 Self::do_schedule(
467 DispatchTime::At(when),
468 maybe_periodic,
469 priority,
470 origin.caller().clone(),
471 T::Preimages::bound(*call)?,
472 )?;
473 Ok(())
474 }
475
476 #[pallet::call_index(1)]
478 #[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]
479 pub fn cancel(origin: OriginFor<T>, when: BlockNumberFor<T>, index: u32) -> DispatchResult {
480 T::ScheduleOrigin::ensure_origin(origin.clone())?;
481 let origin = <T as Config>::RuntimeOrigin::from(origin);
482 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;
483 Ok(())
484 }
485
486 #[pallet::call_index(2)]
488 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
489 pub fn schedule_named(
490 origin: OriginFor<T>,
491 id: TaskName,
492 when: BlockNumberFor<T>,
493 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
494 priority: schedule::Priority,
495 call: Box<<T as Config>::RuntimeCall>,
496 ) -> DispatchResult {
497 T::ScheduleOrigin::ensure_origin(origin.clone())?;
498 let origin = <T as Config>::RuntimeOrigin::from(origin);
499 Self::do_schedule_named(
500 id,
501 DispatchTime::At(when),
502 maybe_periodic,
503 priority,
504 origin.caller().clone(),
505 T::Preimages::bound(*call)?,
506 )?;
507 Ok(())
508 }
509
510 #[pallet::call_index(3)]
512 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]
513 pub fn cancel_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {
514 T::ScheduleOrigin::ensure_origin(origin.clone())?;
515 let origin = <T as Config>::RuntimeOrigin::from(origin);
516 Self::do_cancel_named(Some(origin.caller().clone()), id)?;
517 Ok(())
518 }
519
520 #[pallet::call_index(4)]
522 #[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]
523 pub fn schedule_after(
524 origin: OriginFor<T>,
525 after: BlockNumberFor<T>,
526 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
527 priority: schedule::Priority,
528 call: Box<<T as Config>::RuntimeCall>,
529 ) -> DispatchResult {
530 T::ScheduleOrigin::ensure_origin(origin.clone())?;
531 let origin = <T as Config>::RuntimeOrigin::from(origin);
532 Self::do_schedule(
533 DispatchTime::After(after),
534 maybe_periodic,
535 priority,
536 origin.caller().clone(),
537 T::Preimages::bound(*call)?,
538 )?;
539 Ok(())
540 }
541
542 #[pallet::call_index(5)]
544 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
545 pub fn schedule_named_after(
546 origin: OriginFor<T>,
547 id: TaskName,
548 after: BlockNumberFor<T>,
549 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
550 priority: schedule::Priority,
551 call: Box<<T as Config>::RuntimeCall>,
552 ) -> DispatchResult {
553 T::ScheduleOrigin::ensure_origin(origin.clone())?;
554 let origin = <T as Config>::RuntimeOrigin::from(origin);
555 Self::do_schedule_named(
556 id,
557 DispatchTime::After(after),
558 maybe_periodic,
559 priority,
560 origin.caller().clone(),
561 T::Preimages::bound(*call)?,
562 )?;
563 Ok(())
564 }
565
566 #[pallet::call_index(6)]
579 #[pallet::weight(<T as Config>::WeightInfo::set_retry())]
580 pub fn set_retry(
581 origin: OriginFor<T>,
582 task: TaskAddress<BlockNumberFor<T>>,
583 retries: u8,
584 period: BlockNumberFor<T>,
585 ) -> DispatchResult {
586 T::ScheduleOrigin::ensure_origin(origin.clone())?;
587 let origin = <T as Config>::RuntimeOrigin::from(origin);
588 let (when, index) = task;
589 let agenda = Agenda::<T>::get(when);
590 let scheduled = agenda
591 .get(index as usize)
592 .and_then(Option::as_ref)
593 .ok_or(Error::<T>::NotFound)?;
594 Self::ensure_privilege(origin.caller(), &scheduled.origin)?;
595 Retries::<T>::insert(
596 (when, index),
597 RetryConfig { total_retries: retries, remaining: retries, period },
598 );
599 Self::deposit_event(Event::RetrySet { task, id: None, period, retries });
600 Ok(())
601 }
602
603 #[pallet::call_index(7)]
616 #[pallet::weight(<T as Config>::WeightInfo::set_retry_named())]
617 pub fn set_retry_named(
618 origin: OriginFor<T>,
619 id: TaskName,
620 retries: u8,
621 period: BlockNumberFor<T>,
622 ) -> DispatchResult {
623 T::ScheduleOrigin::ensure_origin(origin.clone())?;
624 let origin = <T as Config>::RuntimeOrigin::from(origin);
625 let (when, agenda_index) = Lookup::<T>::get(&id).ok_or(Error::<T>::NotFound)?;
626 let agenda = Agenda::<T>::get(when);
627 let scheduled = agenda
628 .get(agenda_index as usize)
629 .and_then(Option::as_ref)
630 .ok_or(Error::<T>::NotFound)?;
631 Self::ensure_privilege(origin.caller(), &scheduled.origin)?;
632 Retries::<T>::insert(
633 (when, agenda_index),
634 RetryConfig { total_retries: retries, remaining: retries, period },
635 );
636 Self::deposit_event(Event::RetrySet {
637 task: (when, agenda_index),
638 id: Some(id),
639 period,
640 retries,
641 });
642 Ok(())
643 }
644
645 #[pallet::call_index(8)]
647 #[pallet::weight(<T as Config>::WeightInfo::cancel_retry())]
648 pub fn cancel_retry(
649 origin: OriginFor<T>,
650 task: TaskAddress<BlockNumberFor<T>>,
651 ) -> DispatchResult {
652 T::ScheduleOrigin::ensure_origin(origin.clone())?;
653 let origin = <T as Config>::RuntimeOrigin::from(origin);
654 Self::do_cancel_retry(origin.caller(), task)?;
655 Self::deposit_event(Event::RetryCancelled { task, id: None });
656 Ok(())
657 }
658
659 #[pallet::call_index(9)]
661 #[pallet::weight(<T as Config>::WeightInfo::cancel_retry_named())]
662 pub fn cancel_retry_named(origin: OriginFor<T>, id: TaskName) -> DispatchResult {
663 T::ScheduleOrigin::ensure_origin(origin.clone())?;
664 let origin = <T as Config>::RuntimeOrigin::from(origin);
665 let task = Lookup::<T>::get(&id).ok_or(Error::<T>::NotFound)?;
666 Self::do_cancel_retry(origin.caller(), task)?;
667 Self::deposit_event(Event::RetryCancelled { task, id: Some(id) });
668 Ok(())
669 }
670 }
671}
672
673impl<T: Config> Pallet<T> {
674 pub fn migrate_v1_to_v4() -> Weight {
678 use migration::v1 as old;
679 let mut weight = T::DbWeight::get().reads_writes(1, 1);
680
681 let keys = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
684 for key in keys {
685 weight.saturating_accrue(T::DbWeight::get().reads(1));
686 if let Err(_) = old::Agenda::<T>::try_get(&key) {
687 weight.saturating_accrue(T::DbWeight::get().writes(1));
688 old::Agenda::<T>::remove(&key);
689 log::warn!("Deleted undecodable agenda");
690 }
691 }
692
693 Agenda::<T>::translate::<
694 Vec<Option<ScheduledV1<<T as Config>::RuntimeCall, BlockNumberFor<T>>>>,
695 _,
696 >(|_, agenda| {
697 Some(BoundedVec::truncate_from(
698 agenda
699 .into_iter()
700 .map(|schedule| {
701 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
702
703 schedule.and_then(|schedule| {
704 if let Some(id) = schedule.maybe_id.as_ref() {
705 let name = blake2_256(id);
706 if let Some(item) = old::Lookup::<T>::take(id) {
707 Lookup::<T>::insert(name, item);
708 }
709 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
710 }
711
712 let call = T::Preimages::bound(schedule.call).ok()?;
713
714 if call.lookup_needed() {
715 weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
716 }
717
718 Some(Scheduled {
719 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
720 priority: schedule.priority,
721 call,
722 maybe_periodic: schedule.maybe_periodic,
723 origin: system::RawOrigin::Root.into(),
724 _phantom: Default::default(),
725 })
726 })
727 })
728 .collect::<Vec<_>>(),
729 ))
730 });
731
732 #[allow(deprecated)]
733 frame_support::storage::migration::remove_storage_prefix(
734 Self::name().as_bytes(),
735 b"StorageVersion",
736 &[],
737 );
738
739 StorageVersion::new(4).put::<Self>();
740
741 weight + T::DbWeight::get().writes(2)
742 }
743
744 pub fn migrate_v2_to_v4() -> Weight {
748 use migration::v2 as old;
749 let mut weight = T::DbWeight::get().reads_writes(1, 1);
750
751 let keys = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
754 for key in keys {
755 weight.saturating_accrue(T::DbWeight::get().reads(1));
756 if let Err(_) = old::Agenda::<T>::try_get(&key) {
757 weight.saturating_accrue(T::DbWeight::get().writes(1));
758 old::Agenda::<T>::remove(&key);
759 log::warn!("Deleted undecodable agenda");
760 }
761 }
762
763 Agenda::<T>::translate::<Vec<Option<ScheduledV2Of<T>>>, _>(|_, agenda| {
764 Some(BoundedVec::truncate_from(
765 agenda
766 .into_iter()
767 .map(|schedule| {
768 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
769 schedule.and_then(|schedule| {
770 if let Some(id) = schedule.maybe_id.as_ref() {
771 let name = blake2_256(id);
772 if let Some(item) = old::Lookup::<T>::take(id) {
773 Lookup::<T>::insert(name, item);
774 }
775 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
776 }
777
778 let call = T::Preimages::bound(schedule.call).ok()?;
779 if call.lookup_needed() {
780 weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
781 }
782
783 Some(Scheduled {
784 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
785 priority: schedule.priority,
786 call,
787 maybe_periodic: schedule.maybe_periodic,
788 origin: schedule.origin,
789 _phantom: Default::default(),
790 })
791 })
792 })
793 .collect::<Vec<_>>(),
794 ))
795 });
796
797 #[allow(deprecated)]
798 frame_support::storage::migration::remove_storage_prefix(
799 Self::name().as_bytes(),
800 b"StorageVersion",
801 &[],
802 );
803
804 StorageVersion::new(4).put::<Self>();
805
806 weight + T::DbWeight::get().writes(2)
807 }
808
809 #[allow(deprecated)]
813 pub fn migrate_v3_to_v4() -> Weight {
814 use migration::v3 as old;
815 let mut weight = T::DbWeight::get().reads_writes(2, 1);
816
817 let blocks = old::Agenda::<T>::iter_keys().collect::<Vec<_>>();
820 for block in blocks {
821 weight.saturating_accrue(T::DbWeight::get().reads(1));
822 if let Err(_) = old::Agenda::<T>::try_get(&block) {
823 weight.saturating_accrue(T::DbWeight::get().writes(1));
824 old::Agenda::<T>::remove(&block);
825 log::warn!("Deleted undecodable agenda of block: {:?}", block);
826 }
827 }
828
829 Agenda::<T>::translate::<Vec<Option<ScheduledV3Of<T>>>, _>(|block, agenda| {
830 log::info!("Migrating agenda of block: {:?}", &block);
831 Some(BoundedVec::truncate_from(
832 agenda
833 .into_iter()
834 .map(|schedule| {
835 weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
836 schedule
837 .and_then(|schedule| {
838 if let Some(id) = schedule.maybe_id.as_ref() {
839 let name = blake2_256(id);
840 if let Some(item) = old::Lookup::<T>::take(id) {
841 Lookup::<T>::insert(name, item);
842 log::info!("Migrated name for id: {:?}", id);
843 } else {
844 log::error!("No name in Lookup for id: {:?}", &id);
845 }
846 weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
847 } else {
848 log::info!("Schedule is unnamed");
849 }
850
851 let call = match schedule.call {
852 MaybeHashed::Hash(h) => {
853 let bounded = Bounded::from_legacy_hash(h);
854 if let Err(err) = T::Preimages::peek::<
856 <T as Config>::RuntimeCall,
857 >(&bounded)
858 {
859 log::error!(
860 "Dropping undecodable call {:?}: {:?}",
861 &h,
862 &err
863 );
864 return None
865 }
866 weight.saturating_accrue(T::DbWeight::get().reads(1));
867 log::info!("Migrated call by hash, hash: {:?}", h);
868 bounded
869 },
870 MaybeHashed::Value(v) => {
871 let call = T::Preimages::bound(v)
872 .map_err(|e| {
873 log::error!("Could not bound Call: {:?}", e)
874 })
875 .ok()?;
876 if call.lookup_needed() {
877 weight.saturating_accrue(
878 T::DbWeight::get().reads_writes(0, 1),
879 );
880 }
881 log::info!(
882 "Migrated call by value, hash: {:?}",
883 call.hash()
884 );
885 call
886 },
887 };
888
889 Some(Scheduled {
890 maybe_id: schedule.maybe_id.map(|x| blake2_256(&x[..])),
891 priority: schedule.priority,
892 call,
893 maybe_periodic: schedule.maybe_periodic,
894 origin: schedule.origin,
895 _phantom: Default::default(),
896 })
897 })
898 .or_else(|| {
899 log::info!("Schedule in agenda for block {:?} is empty - nothing to do here.", &block);
900 None
901 })
902 })
903 .collect::<Vec<_>>(),
904 ))
905 });
906
907 #[allow(deprecated)]
908 frame_support::storage::migration::remove_storage_prefix(
909 Self::name().as_bytes(),
910 b"StorageVersion",
911 &[],
912 );
913
914 StorageVersion::new(4).put::<Self>();
915
916 weight + T::DbWeight::get().writes(2)
917 }
918}
919
920impl<T: Config> Pallet<T> {
921 pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {
923 Agenda::<T>::translate::<
924 Vec<
925 Option<
926 Scheduled<
927 TaskName,
928 BoundedCallOf<T>,
929 BlockNumberFor<T>,
930 OldOrigin,
931 T::AccountId,
932 >,
933 >,
934 >,
935 _,
936 >(|_, agenda| {
937 Some(BoundedVec::truncate_from(
938 agenda
939 .into_iter()
940 .map(|schedule| {
941 schedule.map(|schedule| Scheduled {
942 maybe_id: schedule.maybe_id,
943 priority: schedule.priority,
944 call: schedule.call,
945 maybe_periodic: schedule.maybe_periodic,
946 origin: schedule.origin.into(),
947 _phantom: Default::default(),
948 })
949 })
950 .collect::<Vec<_>>(),
951 ))
952 });
953 }
954
955 fn resolve_time(
956 when: DispatchTime<BlockNumberFor<T>>,
957 ) -> Result<BlockNumberFor<T>, DispatchError> {
958 let now = T::BlockNumberProvider::current_block_number();
959 let when = match when {
960 DispatchTime::At(x) => x,
961 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),
964 };
965
966 if when <= now {
967 return Err(Error::<T>::TargetBlockNumberInPast.into())
968 }
969
970 Ok(when)
971 }
972
973 fn place_task(
974 when: BlockNumberFor<T>,
975 what: ScheduledOf<T>,
976 ) -> Result<TaskAddress<BlockNumberFor<T>>, (DispatchError, ScheduledOf<T>)> {
977 let maybe_name = what.maybe_id;
978 let index = Self::push_to_agenda(when, what)?;
979 let address = (when, index);
980 if let Some(name) = maybe_name {
981 Lookup::<T>::insert(name, address)
982 }
983 Self::deposit_event(Event::Scheduled { when: address.0, index: address.1 });
984 Ok(address)
985 }
986
987 fn push_to_agenda(
988 when: BlockNumberFor<T>,
989 what: ScheduledOf<T>,
990 ) -> Result<u32, (DispatchError, ScheduledOf<T>)> {
991 let mut agenda = Agenda::<T>::get(when);
992 let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() {
993 let _ = agenda.try_push(Some(what));
995 agenda.len() as u32 - 1
996 } else {
997 if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) {
998 agenda[hole_index] = Some(what);
999 hole_index as u32
1000 } else {
1001 return Err((DispatchError::Exhausted, what))
1002 }
1003 };
1004 Agenda::<T>::insert(when, agenda);
1005 Ok(index)
1006 }
1007
1008 fn cleanup_agenda(when: BlockNumberFor<T>) {
1011 let mut agenda = Agenda::<T>::get(when);
1012 match agenda.iter().rposition(|i| i.is_some()) {
1013 Some(i) if agenda.len() > i + 1 => {
1014 agenda.truncate(i + 1);
1015 Agenda::<T>::insert(when, agenda);
1016 },
1017 Some(_) => {},
1018 None => {
1019 Agenda::<T>::remove(when);
1020 },
1021 }
1022 }
1023
1024 fn do_schedule(
1025 when: DispatchTime<BlockNumberFor<T>>,
1026 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1027 priority: schedule::Priority,
1028 origin: T::PalletsOrigin,
1029 call: BoundedCallOf<T>,
1030 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1031 let when = Self::resolve_time(when)?;
1032
1033 let lookup_hash = call.lookup_hash();
1034
1035 let maybe_periodic = maybe_periodic
1037 .filter(|p| p.1 > 1 && !p.0.is_zero())
1038 .map(|(p, c)| (p, c - 1));
1040 let task = Scheduled {
1041 maybe_id: None,
1042 priority,
1043 call,
1044 maybe_periodic,
1045 origin,
1046 _phantom: PhantomData,
1047 };
1048 let res = Self::place_task(when, task).map_err(|x| x.0)?;
1049
1050 if let Some(hash) = lookup_hash {
1051 T::Preimages::request(&hash);
1053 }
1054
1055 Ok(res)
1056 }
1057
1058 fn do_cancel(
1059 origin: Option<T::PalletsOrigin>,
1060 (when, index): TaskAddress<BlockNumberFor<T>>,
1061 ) -> Result<(), DispatchError> {
1062 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {
1063 agenda.get_mut(index as usize).map_or(
1064 Ok(None),
1065 |s| -> Result<Option<Scheduled<_, _, _, _, _>>, DispatchError> {
1066 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
1067 Self::ensure_privilege(o, &s.origin)?;
1068 };
1069 Ok(s.take())
1070 },
1071 )
1072 })?;
1073 if let Some(s) = scheduled {
1074 T::Preimages::drop(&s.call);
1075 if let Some(id) = s.maybe_id {
1076 Lookup::<T>::remove(id);
1077 }
1078 Retries::<T>::remove((when, index));
1079 Self::cleanup_agenda(when);
1080 Self::deposit_event(Event::Canceled { when, index });
1081 Ok(())
1082 } else {
1083 return Err(Error::<T>::NotFound.into())
1084 }
1085 }
1086
1087 fn do_reschedule(
1088 (when, index): TaskAddress<BlockNumberFor<T>>,
1089 new_time: DispatchTime<BlockNumberFor<T>>,
1090 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1091 let new_time = Self::resolve_time(new_time)?;
1092
1093 if new_time == when {
1094 return Err(Error::<T>::RescheduleNoChange.into())
1095 }
1096
1097 let task = Agenda::<T>::try_mutate(when, |agenda| {
1098 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
1099 ensure!(!matches!(task, Some(Scheduled { maybe_id: Some(_), .. })), Error::<T>::Named);
1100 task.take().ok_or(Error::<T>::NotFound)
1101 })?;
1102 Self::cleanup_agenda(when);
1103 Self::deposit_event(Event::Canceled { when, index });
1104
1105 Self::place_task(new_time, task).map_err(|x| x.0)
1106 }
1107
1108 fn do_schedule_named(
1109 id: TaskName,
1110 when: DispatchTime<BlockNumberFor<T>>,
1111 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1112 priority: schedule::Priority,
1113 origin: T::PalletsOrigin,
1114 call: BoundedCallOf<T>,
1115 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1116 if Lookup::<T>::contains_key(&id) {
1118 return Err(Error::<T>::FailedToSchedule.into())
1119 }
1120
1121 let when = Self::resolve_time(when)?;
1122
1123 let lookup_hash = call.lookup_hash();
1124
1125 let maybe_periodic = maybe_periodic
1127 .filter(|p| p.1 > 1 && !p.0.is_zero())
1128 .map(|(p, c)| (p, c - 1));
1130
1131 let task = Scheduled {
1132 maybe_id: Some(id),
1133 priority,
1134 call,
1135 maybe_periodic,
1136 origin,
1137 _phantom: Default::default(),
1138 };
1139 let res = Self::place_task(when, task).map_err(|x| x.0)?;
1140
1141 if let Some(hash) = lookup_hash {
1142 T::Preimages::request(&hash);
1144 }
1145
1146 Ok(res)
1147 }
1148
1149 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: TaskName) -> DispatchResult {
1150 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {
1151 if let Some((when, index)) = lookup.take() {
1152 let i = index as usize;
1153 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
1154 if let Some(s) = agenda.get_mut(i) {
1155 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
1156 Self::ensure_privilege(o, &s.origin)?;
1157 Retries::<T>::remove((when, index));
1158 T::Preimages::drop(&s.call);
1159 }
1160 *s = None;
1161 }
1162 Ok(())
1163 })?;
1164 Self::cleanup_agenda(when);
1165 Self::deposit_event(Event::Canceled { when, index });
1166 Ok(())
1167 } else {
1168 return Err(Error::<T>::NotFound.into())
1169 }
1170 })
1171 }
1172
1173 fn do_reschedule_named(
1174 id: TaskName,
1175 new_time: DispatchTime<BlockNumberFor<T>>,
1176 ) -> Result<TaskAddress<BlockNumberFor<T>>, DispatchError> {
1177 let new_time = Self::resolve_time(new_time)?;
1178
1179 let lookup = Lookup::<T>::get(id);
1180 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;
1181
1182 if new_time == when {
1183 return Err(Error::<T>::RescheduleNoChange.into())
1184 }
1185
1186 let task = Agenda::<T>::try_mutate(when, |agenda| {
1187 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
1188 task.take().ok_or(Error::<T>::NotFound)
1189 })?;
1190 Self::cleanup_agenda(when);
1191 Self::deposit_event(Event::Canceled { when, index });
1192 Self::place_task(new_time, task).map_err(|x| x.0)
1193 }
1194
1195 fn do_cancel_retry(
1196 origin: &T::PalletsOrigin,
1197 (when, index): TaskAddress<BlockNumberFor<T>>,
1198 ) -> Result<(), DispatchError> {
1199 let agenda = Agenda::<T>::get(when);
1200 let scheduled = agenda
1201 .get(index as usize)
1202 .and_then(Option::as_ref)
1203 .ok_or(Error::<T>::NotFound)?;
1204 Self::ensure_privilege(origin, &scheduled.origin)?;
1205 Retries::<T>::remove((when, index));
1206 Ok(())
1207 }
1208}
1209
1210enum ServiceTaskError {
1211 Unavailable,
1213 Overweight,
1215}
1216use ServiceTaskError::*;
1217
1218impl<T: Config> Pallet<T> {
1219 fn service_agendas(weight: &mut WeightMeter, now: BlockNumberFor<T>, max: u32) {
1221 if weight.try_consume(T::WeightInfo::service_agendas_base()).is_err() {
1222 return
1223 }
1224
1225 let mut incomplete_since = now + One::one();
1226 let mut when = IncompleteSince::<T>::take().unwrap_or(now);
1227 let mut is_first = true; let max_items = T::MaxScheduledPerBlock::get();
1230 let mut count_down = max;
1231 let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items);
1232 while count_down > 0 && when <= now && weight.can_consume(service_agenda_base_weight) {
1233 if !Self::service_agenda(weight, is_first, now, when, u32::MAX) {
1234 incomplete_since = incomplete_since.min(when);
1235 }
1236 is_first = false;
1237 when.saturating_inc();
1238 count_down.saturating_dec();
1239 }
1240 incomplete_since = incomplete_since.min(when);
1241 if incomplete_since <= now {
1242 Self::deposit_event(Event::AgendaIncomplete { when: incomplete_since });
1243 IncompleteSince::<T>::put(incomplete_since);
1244 } else {
1245 IncompleteSince::<T>::put(now + One::one());
1250 }
1251 }
1252
1253 fn service_agenda(
1256 weight: &mut WeightMeter,
1257 mut is_first: bool,
1258 now: BlockNumberFor<T>,
1259 when: BlockNumberFor<T>,
1260 max: u32,
1261 ) -> bool {
1262 let mut agenda = Agenda::<T>::get(when);
1263 let mut ordered = agenda
1264 .iter()
1265 .enumerate()
1266 .filter_map(|(index, maybe_item)| {
1267 maybe_item.as_ref().map(|item| (index as u32, item.priority))
1268 })
1269 .collect::<Vec<_>>();
1270 ordered.sort_by_key(|k| k.1);
1271 let within_limit = weight
1272 .try_consume(T::WeightInfo::service_agenda_base(ordered.len() as u32))
1273 .is_ok();
1274 debug_assert!(within_limit, "weight limit should have been checked in advance");
1275
1276 let mut postponed = (ordered.len() as u32).saturating_sub(max);
1278 let mut dropped = 0;
1280
1281 for (agenda_index, _) in ordered.into_iter().take(max as usize) {
1282 let Some(task) = agenda[agenda_index as usize].take() else { continue };
1283 let base_weight = T::WeightInfo::service_task(
1284 task.call.lookup_len().map(|x| x as usize),
1285 task.maybe_id.is_some(),
1286 task.maybe_periodic.is_some(),
1287 );
1288 if !weight.can_consume(base_weight) {
1289 postponed += 1;
1290 agenda[agenda_index as usize] = Some(task);
1291 break
1292 }
1293 let result = Self::service_task(weight, now, when, agenda_index, is_first, task);
1294 agenda[agenda_index as usize] = match result {
1295 Err((Unavailable, slot)) => {
1296 dropped += 1;
1297 slot
1298 },
1299 Err((Overweight, slot)) => {
1300 postponed += 1;
1301 slot
1302 },
1303 Ok(()) => {
1304 is_first = false;
1305 None
1306 },
1307 };
1308 }
1309 if postponed > 0 || dropped > 0 {
1310 Agenda::<T>::insert(when, agenda);
1311 } else {
1312 Agenda::<T>::remove(when);
1313 }
1314
1315 postponed == 0
1316 }
1317
1318 fn service_task(
1325 weight: &mut WeightMeter,
1326 now: BlockNumberFor<T>,
1327 when: BlockNumberFor<T>,
1328 agenda_index: u32,
1329 is_first: bool,
1330 mut task: ScheduledOf<T>,
1331 ) -> Result<(), (ServiceTaskError, Option<ScheduledOf<T>>)> {
1332 if let Some(ref id) = task.maybe_id {
1333 Lookup::<T>::remove(id);
1334 }
1335
1336 let (call, lookup_len) = match T::Preimages::peek(&task.call) {
1337 Ok(c) => c,
1338 Err(_) => {
1339 Self::deposit_event(Event::CallUnavailable {
1340 task: (when, agenda_index),
1341 id: task.maybe_id,
1342 });
1343
1344 T::Preimages::drop(&task.call);
1347
1348 let _ = weight.try_consume(T::WeightInfo::service_task(
1350 task.call.lookup_len().map(|x| x as usize),
1351 task.maybe_id.is_some(),
1352 task.maybe_periodic.is_some(),
1353 ));
1354
1355 return Err((Unavailable, Some(task)))
1356 },
1357 };
1358
1359 let _ = weight.try_consume(T::WeightInfo::service_task(
1360 lookup_len.map(|x| x as usize),
1361 task.maybe_id.is_some(),
1362 task.maybe_periodic.is_some(),
1363 ));
1364
1365 match Self::execute_dispatch(weight, task.origin.clone(), call) {
1366 Err(()) if is_first => {
1367 T::Preimages::drop(&task.call);
1368 Self::deposit_event(Event::PermanentlyOverweight {
1369 task: (when, agenda_index),
1370 id: task.maybe_id,
1371 });
1372 Err((Unavailable, Some(task)))
1373 },
1374 Err(()) => Err((Overweight, Some(task))),
1375 Ok(result) => {
1376 let failed = result.is_err();
1377 let maybe_retry_config = Retries::<T>::take((when, agenda_index));
1378 Self::deposit_event(Event::Dispatched {
1379 task: (when, agenda_index),
1380 id: task.maybe_id,
1381 result,
1382 });
1383
1384 match maybe_retry_config {
1385 Some(retry_config) if failed => {
1386 Self::schedule_retry(weight, now, when, agenda_index, &task, retry_config);
1387 },
1388 _ => {},
1389 }
1390
1391 if let &Some((period, count)) = &task.maybe_periodic {
1392 if count > 1 {
1393 task.maybe_periodic = Some((period, count - 1));
1394 } else {
1395 task.maybe_periodic = None;
1396 }
1397 let wake = now.saturating_add(period);
1398 match Self::place_task(wake, task) {
1399 Ok(new_address) =>
1400 if let Some(retry_config) = maybe_retry_config {
1401 Retries::<T>::insert(new_address, retry_config);
1402 },
1403 Err((_, task)) => {
1404 T::Preimages::drop(&task.call);
1407 Self::deposit_event(Event::PeriodicFailed {
1408 task: (when, agenda_index),
1409 id: task.maybe_id,
1410 });
1411 },
1412 }
1413 } else {
1414 T::Preimages::drop(&task.call);
1415 }
1416 Ok(())
1417 },
1418 }
1419 }
1420
1421 fn execute_dispatch(
1430 weight: &mut WeightMeter,
1431 origin: T::PalletsOrigin,
1432 call: <T as Config>::RuntimeCall,
1433 ) -> Result<DispatchResult, ()> {
1434 let base_weight = match origin.as_system_ref() {
1435 Some(&RawOrigin::Signed(_)) => T::WeightInfo::execute_dispatch_signed(),
1436 _ => T::WeightInfo::execute_dispatch_unsigned(),
1437 };
1438 let call_weight = call.get_dispatch_info().call_weight;
1439 let max_weight = base_weight.saturating_add(call_weight);
1441
1442 if !weight.can_consume(max_weight) {
1443 return Err(())
1444 }
1445
1446 let dispatch_origin = origin.into();
1447 let (maybe_actual_call_weight, result) = match call.dispatch(dispatch_origin) {
1448 Ok(post_info) => (post_info.actual_weight, Ok(())),
1449 Err(error_and_info) =>
1450 (error_and_info.post_info.actual_weight, Err(error_and_info.error)),
1451 };
1452 let call_weight = maybe_actual_call_weight.unwrap_or(call_weight);
1453 let _ = weight.try_consume(base_weight);
1454 let _ = weight.try_consume(call_weight);
1455 Ok(result)
1456 }
1457
1458 fn schedule_retry(
1466 weight: &mut WeightMeter,
1467 now: BlockNumberFor<T>,
1468 when: BlockNumberFor<T>,
1469 agenda_index: u32,
1470 task: &ScheduledOf<T>,
1471 retry_config: RetryConfig<BlockNumberFor<T>>,
1472 ) {
1473 if weight
1474 .try_consume(T::WeightInfo::schedule_retry(T::MaxScheduledPerBlock::get()))
1475 .is_err()
1476 {
1477 Self::deposit_event(Event::RetryFailed {
1478 task: (when, agenda_index),
1479 id: task.maybe_id,
1480 });
1481 return;
1482 }
1483
1484 let RetryConfig { total_retries, mut remaining, period } = retry_config;
1485 remaining = match remaining.checked_sub(1) {
1486 Some(n) => n,
1487 None => return,
1488 };
1489 let wake = now.saturating_add(period);
1490 match Self::place_task(wake, task.as_retry()) {
1491 Ok(address) => {
1492 Retries::<T>::insert(address, RetryConfig { total_retries, remaining, period });
1495 },
1496 Err((_, task)) => {
1497 T::Preimages::drop(&task.call);
1500 Self::deposit_event(Event::RetryFailed {
1501 task: (when, agenda_index),
1502 id: task.maybe_id,
1503 });
1504 },
1505 }
1506 }
1507
1508 fn ensure_privilege(
1512 left: &<T as Config>::PalletsOrigin,
1513 right: &<T as Config>::PalletsOrigin,
1514 ) -> Result<(), DispatchError> {
1515 if matches!(T::OriginPrivilegeCmp::cmp_privilege(left, right), Some(Ordering::Less) | None)
1516 {
1517 return Err(BadOrigin.into());
1518 }
1519 Ok(())
1520 }
1521}
1522
1523#[allow(deprecated)]
1524impl<T: Config> schedule::v2::Anon<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1525 for Pallet<T>
1526{
1527 type Address = TaskAddress<BlockNumberFor<T>>;
1528 type Hash = T::Hash;
1529
1530 fn schedule(
1531 when: DispatchTime<BlockNumberFor<T>>,
1532 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1533 priority: schedule::Priority,
1534 origin: T::PalletsOrigin,
1535 call: CallOrHashOf<T>,
1536 ) -> Result<Self::Address, DispatchError> {
1537 let call = call.as_value().ok_or(DispatchError::CannotLookup)?;
1538 let call = T::Preimages::bound(call)?.transmute();
1539 Self::do_schedule(when, maybe_periodic, priority, origin, call)
1540 }
1541
1542 fn cancel((when, index): Self::Address) -> Result<(), ()> {
1543 Self::do_cancel(None, (when, index)).map_err(|_| ())
1544 }
1545
1546 fn reschedule(
1547 address: Self::Address,
1548 when: DispatchTime<BlockNumberFor<T>>,
1549 ) -> Result<Self::Address, DispatchError> {
1550 Self::do_reschedule(address, when)
1551 }
1552
1553 fn next_dispatch_time((when, index): Self::Address) -> Result<BlockNumberFor<T>, ()> {
1554 Agenda::<T>::get(when).get(index as usize).ok_or(()).map(|_| when)
1555 }
1556}
1557
1558#[allow(deprecated)]
1560impl<T: Config> schedule::v2::Named<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1561 for Pallet<T>
1562{
1563 type Address = TaskAddress<BlockNumberFor<T>>;
1564 type Hash = T::Hash;
1565
1566 fn schedule_named(
1567 id: Vec<u8>,
1568 when: DispatchTime<BlockNumberFor<T>>,
1569 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1570 priority: schedule::Priority,
1571 origin: T::PalletsOrigin,
1572 call: CallOrHashOf<T>,
1573 ) -> Result<Self::Address, ()> {
1574 let call = call.as_value().ok_or(())?;
1575 let call = T::Preimages::bound(call).map_err(|_| ())?.transmute();
1576 let name = blake2_256(&id[..]);
1577 Self::do_schedule_named(name, when, maybe_periodic, priority, origin, call).map_err(|_| ())
1578 }
1579
1580 fn cancel_named(id: Vec<u8>) -> Result<(), ()> {
1581 let name = blake2_256(&id[..]);
1582 Self::do_cancel_named(None, name).map_err(|_| ())
1583 }
1584
1585 fn reschedule_named(
1586 id: Vec<u8>,
1587 when: DispatchTime<BlockNumberFor<T>>,
1588 ) -> Result<Self::Address, DispatchError> {
1589 let name = blake2_256(&id[..]);
1590 Self::do_reschedule_named(name, when)
1591 }
1592
1593 fn next_dispatch_time(id: Vec<u8>) -> Result<BlockNumberFor<T>, ()> {
1594 let name = blake2_256(&id[..]);
1595 Lookup::<T>::get(name)
1596 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))
1597 .ok_or(())
1598 }
1599}
1600
1601impl<T: Config> schedule::v3::Anon<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1602 for Pallet<T>
1603{
1604 type Address = TaskAddress<BlockNumberFor<T>>;
1605 type Hasher = T::Hashing;
1606
1607 fn schedule(
1608 when: DispatchTime<BlockNumberFor<T>>,
1609 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1610 priority: schedule::Priority,
1611 origin: T::PalletsOrigin,
1612 call: BoundedCallOf<T>,
1613 ) -> Result<Self::Address, DispatchError> {
1614 Self::do_schedule(when, maybe_periodic, priority, origin, call)
1615 }
1616
1617 fn cancel((when, index): Self::Address) -> Result<(), DispatchError> {
1618 Self::do_cancel(None, (when, index)).map_err(map_err_to_v3_err::<T>)
1619 }
1620
1621 fn reschedule(
1622 address: Self::Address,
1623 when: DispatchTime<BlockNumberFor<T>>,
1624 ) -> Result<Self::Address, DispatchError> {
1625 Self::do_reschedule(address, when).map_err(map_err_to_v3_err::<T>)
1626 }
1627
1628 fn next_dispatch_time(
1629 (when, index): Self::Address,
1630 ) -> Result<BlockNumberFor<T>, DispatchError> {
1631 Agenda::<T>::get(when)
1632 .get(index as usize)
1633 .ok_or(DispatchError::Unavailable)
1634 .map(|_| when)
1635 }
1636}
1637
1638use schedule::v3::TaskName;
1639
1640impl<T: Config> schedule::v3::Named<BlockNumberFor<T>, <T as Config>::RuntimeCall, T::PalletsOrigin>
1641 for Pallet<T>
1642{
1643 type Address = TaskAddress<BlockNumberFor<T>>;
1644 type Hasher = T::Hashing;
1645
1646 fn schedule_named(
1647 id: TaskName,
1648 when: DispatchTime<BlockNumberFor<T>>,
1649 maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
1650 priority: schedule::Priority,
1651 origin: T::PalletsOrigin,
1652 call: BoundedCallOf<T>,
1653 ) -> Result<Self::Address, DispatchError> {
1654 Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call)
1655 }
1656
1657 fn cancel_named(id: TaskName) -> Result<(), DispatchError> {
1658 Self::do_cancel_named(None, id).map_err(map_err_to_v3_err::<T>)
1659 }
1660
1661 fn reschedule_named(
1662 id: TaskName,
1663 when: DispatchTime<BlockNumberFor<T>>,
1664 ) -> Result<Self::Address, DispatchError> {
1665 Self::do_reschedule_named(id, when).map_err(map_err_to_v3_err::<T>)
1666 }
1667
1668 fn next_dispatch_time(id: TaskName) -> Result<BlockNumberFor<T>, DispatchError> {
1669 Lookup::<T>::get(id)
1670 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))
1671 .ok_or(DispatchError::Unavailable)
1672 }
1673}
1674
1675fn map_err_to_v3_err<T: Config>(err: DispatchError) -> DispatchError {
1677 if err == DispatchError::from(Error::<T>::NotFound) {
1678 DispatchError::Unavailable
1679 } else {
1680 err
1681 }
1682}