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