git.delta.rocks / unique-network / refs/commits / 70da175fb9fe

difftreelog

source

pallets/scheduler/src/lib.rs28.9 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! # Scheduler19//! A module for scheduling dispatches.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Module`]24//!25//! ## Overview26//!27//! This module exposes capabilities for scheduling dispatches to occur at a28//! specified block number or at a specified period. These scheduled dispatches29//! may be named or anonymous and may be canceled.30//!31//! **NOTE:** The scheduled calls will be dispatched with the default filter32//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin33//! except root which will get no filter. And not the filter contained in origin34//! use to call `fn schedule`.35//!36//! If a call is scheduled using proxy or whatever mecanism which adds filter,37//! then those filter will not be used when dispatching the schedule call.38//!39//! ## Interface40//!41//! ### Dispatchable Functions42//!43//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a44//!   specified block and with a specified priority.45//! * `cancel` - cancel a scheduled dispatch, specified by block number and46//!   index.47//! * `schedule_named` - augments the `schedule` interface with an additional48//!   `Vec<u8>` parameter that can be used for identification.49//! * `cancel_named` - the named complement to the cancel function.5051// Ensure we're `no_std` when compiling for Wasm.52#![cfg_attr(not(feature = "std"), no_std)]53#![allow(clippy::type_complexity, clippy::boxed_local, clippy::unused_unit)]5455mod benchmarking;56pub mod weights;5758use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow};59use codec::{Encode, Decode, Codec};60use sp_runtime::{61	RuntimeDebug,62	traits::{Zero, One, BadOrigin, Saturating},63};64use frame_support::{65	decl_module, decl_storage, decl_event, decl_error, IterableStorageMap,66	dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter},67	traits::{68		Get,69		schedule::{self, DispatchTime},70		OriginTrait, EnsureOrigin, IsType,71	},72	weights::{GetDispatchInfo, Weight},73};74use frame_system::{self as system, ensure_signed};75pub use weights::WeightInfo;76use up_sponsorship::SponsorshipHandler;7778/// Our pallet's configuration trait. All our types and constants go in here. If the79/// pallet is dependent on specific other pallets, then their configuration traits80/// should be added to our implied traits list.81///82/// `system::Config` should always be included in our implied traits.83/// //84pub trait Config: system::Config {85	/// The overarching event type.86	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;8788	/// The aggregated origin which the dispatch will take.89	type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>90		+ From<Self::PalletsOrigin>91		+ IsType<<Self as system::Config>::Origin>;9293	/// The caller origin, overarching type of all pallets origins.94	type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq;9596	/// The aggregated call type.97	type Call: Parameter98		+ Dispatchable<Origin = <Self as Config>::Origin>99		+ GetDispatchInfo100		+ From<system::Call<Self>>;101102	/// The maximum weight that may be scheduled per block for any dispatchables of less priority103	/// than `schedule::HARD_DEADLINE`.104	type MaximumWeight: Get<Weight>;105106	/// Required origin to schedule or cancel calls.107	type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;108109	/// The maximum number of scheduled calls in the queue for a single block.110	/// Not strictly enforced, but used for weight estimation.111	type MaxScheduledPerBlock: Get<u32>;112113	/// Sponsoring function114	type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;115116	/// Weight information for extrinsics in this pallet.117	type WeightInfo: WeightInfo;118}119120// pub type SelfWeightInfo<T> = <T as system::Config>::WeightInfo;121122/// Just a simple index for naming period tasks.123pub type PeriodicIndex = u32;124/// The location of a scheduled task that can be used to remove it.125pub type TaskAddress<BlockNumber> = (BlockNumber, u32);126127#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]128#[derive(Clone, RuntimeDebug, Encode, Decode)]129struct ScheduledV1<Call, BlockNumber> {130	maybe_id: Option<Vec<u8>>,131	priority: schedule::Priority,132	call: Call,133	maybe_periodic: Option<schedule::Period<BlockNumber>>,134}135136/// Information regarding an item to be executed in the future.137#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]138#[derive(Clone, RuntimeDebug, Encode, Decode)]139pub struct ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId> {140	/// The unique identity for this task, if there is one.141	maybe_id: Option<Vec<u8>>,142	/// This task's priority.143	priority: schedule::Priority,144	/// The call to be dispatched.145	call: Call,146	/// If the call is periodic, then this points to the information concerning that.147	maybe_periodic: Option<schedule::Period<BlockNumber>>,148	/// The origin to dispatch the call.149	origin: PalletsOrigin,150	_phantom: PhantomData<AccountId>,151}152153/// The current version of Scheduled struct.154pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =155	ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId>;156157// A value placed in storage that represents the current version of the Scheduler storage.158// This value is used by the `on_runtime_upgrade` logic to determine whether we run159// storage migration logic.160#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]161enum Releases {162	V1,163	V2,164}165166impl Default for Releases {167	fn default() -> Self {168		Releases::V1169	}170}171172#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]173pub struct CallSpec {174	module: u32,175	method: u32,176}177178decl_storage! {179	trait Store for Module<T: Config> as Scheduler {180		/// Items to be executed, indexed by the block number that they should be executed on.181		pub Agenda: map hasher(twox_64_concat) T::BlockNumber182			=> Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>;183184		pub SpecAgenda: map hasher(twox_64_concat) T::BlockNumber185			=> Vec<Option<CallSpec>>;186187		/// Lookup from identity to the block number and index of the task.188		Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;189190		/// Storage version of the pallet.191		///192		/// New networks start with last version.193		StorageVersion build(|_| Releases::V2): Releases;194	}195}196197decl_event!(198	pub enum Event<T> where <T as system::Config>::BlockNumber {199		/// Scheduled some task. \[when, index\]200		Scheduled(BlockNumber, u32),201		/// Canceled some task. \[when, index\]202		Canceled(BlockNumber, u32),203		/// Dispatched some task. \[task, id, result\]204		Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),205	}206);207208decl_error! {209	pub enum Error for Module<T: Config> {210		/// Failed to schedule a call211		FailedToSchedule,212		/// Cannot find the scheduled call.213		NotFound,214		/// Given target block number is in the past.215		TargetBlockNumberInPast,216		/// Reschedule failed because it does not change scheduled time.217		RescheduleNoChange,218	}219}220221decl_module! {222	/// Scheduler module declaration.223	pub struct Module<T: Config> for enum Call224	where225		origin: <T as system::Config>::Origin226	{227		type Error = Error<T>;228		fn deposit_event() = default;229230231		/// Anonymously schedule a task.232		///233		/// # <weight>234		/// - S = Number of already scheduled calls235		/// - Base Weight: 22.29 + .126 * S µs236		/// - DB Weight:237		///     - Read: Agenda238		///     - Write: Agenda239		/// - Will use base weight of 25 which should be good for up to 30 scheduled calls240		/// # </weight>241		#[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]242		fn schedule(origin,243			when: T::BlockNumber,244			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,245			priority: schedule::Priority,246			call: Box<<T as Config>::Call>,247		)248		{249			let origin = <T as Config>::Origin::from(origin);250			Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?;251		}252253		/// Cancel an anonymously scheduled task.254		///255		/// # <weight>256		/// - S = Number of already scheduled calls257		/// - Base Weight: 22.15 + 2.869 * S µs258		/// - DB Weight:259		///     - Read: Agenda260		///     - Write: Agenda, Lookup261		/// - Will use base weight of 100 which should be good for up to 30 scheduled calls262		/// # </weight>263		#[weight = <T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get())]264		fn cancel(origin, when: T::BlockNumber, index: u32) {265			T::ScheduleOrigin::ensure_origin(origin.clone())?;266			let origin = <T as Config>::Origin::from(origin);267			Self::do_cancel(Some(origin.caller().clone()), (when, index))?;268		}269270		/// Schedule a named task.271		///272		/// # <weight>273		/// - S = Number of already scheduled calls274		/// - Base Weight: 29.6 + .159 * S µs275		/// - DB Weight:276		///     - Read: Agenda, Lookup277		///     - Write: Agenda, Lookup278		/// - Will use base weight of 35 which should be good for more than 30 scheduled calls279		/// # </weight>280		#[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]281		fn schedule_named(origin,282			id: Vec<u8>,283			when: T::BlockNumber,284			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,285			priority: schedule::Priority,286			call: Box<<T as Config>::Call>,287		) {288			T::ScheduleOrigin::ensure_origin(origin.clone())?;289			let origin = <T as Config>::Origin::from(origin);290			Self::do_schedule_named(291				id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call292			)?;293		}294295		/// Cancel a named scheduled task.296		///297		/// # <weight>298		/// - S = Number of already scheduled calls299		/// - Base Weight: 24.91 + 2.907 * S µs300		/// - DB Weight:301		///     - Read: Agenda, Lookup302		///     - Write: Agenda, Lookup303		/// - Will use base weight of 100 which should be good for up to 30 scheduled calls304		/// # </weight>305		#[weight = <T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())]306		fn cancel_named(origin, id: Vec<u8>) {307			T::ScheduleOrigin::ensure_origin(origin.clone())?;308			let origin = <T as Config>::Origin::from(origin);309			Self::do_cancel_named(Some(origin.caller().clone()), id)?;310		}311312		/// Anonymously schedule a task after a delay.313		///314		/// # <weight>315		/// Same as [`schedule`].316		/// # </weight>317		#[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]318		fn schedule_after(origin,319			after: T::BlockNumber,320			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,321			priority: schedule::Priority,322			call: Box<<T as Config>::Call>,323		) {324			T::ScheduleOrigin::ensure_origin(origin.clone())?;325			let origin = <T as Config>::Origin::from(origin);326			Self::do_schedule(327				DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call328			)?;329		}330331		/// Schedule a named task after a delay.332		///333		/// # <weight>334		/// Same as [`schedule_named`].335		/// # </weight>336		#[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]337		fn schedule_named_after(origin,338			id: Vec<u8>,339			after: T::BlockNumber,340			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,341			priority: schedule::Priority,342			call: Box<<T as Config>::Call>,343		) {344			T::ScheduleOrigin::ensure_origin(origin.clone())?;345			let origin = <T as Config>::Origin::from(origin);346			Self::do_schedule_named(347				id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call348			)?;349		}350351		/// Execute the scheduled calls352		///353		/// # <weight>354		/// - S = Number of already scheduled calls355		/// - N = Named scheduled calls356		/// - P = Periodic Calls357		/// - Base Weight: 9.243 + 23.45 * S µs358		/// - DB Weight:359		///     - Read: Agenda + Lookup * N + Agenda(Future) * P360		///     - Write: Agenda + Lookup * N  + Agenda(future) * P361		/// # </weight>362		fn on_initialize(now: T::BlockNumber) -> Weight {363			let limit = T::MaximumWeight::get();364			let mut queued = Agenda::<T>::take(now).into_iter()365				.enumerate()366				.filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))367				.collect::<Vec<_>>();368			if queued.len() as u32 > T::MaxScheduledPerBlock::get() {369				log::warn!(370					target: "runtime::scheduler",371					"Warning: This block has more items queued in Scheduler than \372					expected from the runtime configuration. An update might be needed."373				);374			}375			queued.sort_by_key(|(_, s)| s.priority);376			let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)377			let mut total_weight: Weight = 0;378			queued.into_iter()379				.enumerate()380				.scan(base_weight, |cumulative_weight, (order, (index, s))| {381					*cumulative_weight = cumulative_weight382						.saturating_add(s.call.get_dispatch_info().weight);383384					let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(385						s.origin.clone()386					).into();387388					if ensure_signed(origin).is_ok() {389						 // AccountData for inner call origin accountdata.390						*cumulative_weight = cumulative_weight391							.saturating_add(T::DbWeight::get().reads_writes(1, 1));392					}393394					if s.maybe_id.is_some() {395						// Remove/Modify Lookup396						*cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1));397					}398					if s.maybe_periodic.is_some() {399						// Read/Write Agenda for future block400						*cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));401					}402403					Some((order, index, *cumulative_weight, s))404				})405				.filter_map(|(order, index, cumulative_weight, mut s)| {406					// We allow a scheduled call if any is true:407					// - It's priority is `HARD_DEADLINE`408					// - It does not push the weight past the limit.409					// - It is the first item in the schedule410					if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 {411412						let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(413							s.origin.clone()414						).into();415						let sender = ensure_signed(origin).unwrap_or_default();416						let who_will_pay = T::SponsorshipHandler::get_sponsor(&sender, &s.call).unwrap_or(sender);417						let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));418						let r = s.call.clone().dispatch(sponsor.into());419						let maybe_id = s.maybe_id.clone();420						if let Some((period, count)) = s.maybe_periodic {421							if count > 1 {422								s.maybe_periodic = Some((period, count - 1));423							} else {424								s.maybe_periodic = None;425							}426							let next = now + period;427							// If scheduled is named, place it's information in `Lookup`428							if let Some(ref id) = s.maybe_id {429								let next_index = Agenda::<T>::decode_len(now + period).unwrap_or(0);430								Lookup::<T>::insert(id, (next, next_index as u32));431							}432							Agenda::<T>::append(next, Some(s));433						} else if let Some(ref id) = s.maybe_id {434									  Lookup::<T>::remove(id);435								  }436						Self::deposit_event(RawEvent::Dispatched(437							(now, index),438							maybe_id,439							r.map(|_| ()).map_err(|e| e.error)440						));441						total_weight = cumulative_weight;442						None443					} else {444						Some(Some(s))445					}446				})447				.for_each(|unused| {448					let next = now + One::one();449					Agenda::<T>::append(next, unused);450				});451452			total_weight453		}454	}455}456457impl<T: Config> Module<T> {458	/// Migrate storage format from V1 to V2.459	/// Return true if migration is performed.460	pub fn migrate_v1_to_t2() -> bool {461		if StorageVersion::get() == Releases::V1 {462			StorageVersion::put(Releases::V2);463464			Agenda::<T>::translate::<465				Vec<Option<ScheduledV1<<T as Config>::Call, T::BlockNumber>>>,466				_,467			>(|_, agenda| {468				Some(469					agenda470						.into_iter()471						.map(|schedule| {472							schedule.map(|schedule| ScheduledV2 {473								maybe_id: schedule.maybe_id,474								priority: schedule.priority,475								call: schedule.call,476								maybe_periodic: schedule.maybe_periodic,477								origin: system::RawOrigin::Root.into(),478								_phantom: Default::default(),479							})480						})481						.collect::<Vec<_>>(),482				)483			});484485			true486		} else {487			false488		}489	}490491	/// Helper to migrate scheduler when the pallet origin type has changed.492	pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {493		Agenda::<T>::translate::<494			Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, OldOrigin, T::AccountId>>>,495			_,496		>(|_, agenda| {497			Some(498				agenda499					.into_iter()500					.map(|schedule| {501						schedule.map(|schedule| Scheduled {502							maybe_id: schedule.maybe_id,503							priority: schedule.priority,504							call: schedule.call,505							maybe_periodic: schedule.maybe_periodic,506							origin: schedule.origin.into(),507							_phantom: Default::default(),508						})509					})510					.collect::<Vec<_>>(),511			)512		});513	}514515	fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {516		let now = frame_system::Pallet::<T>::block_number();517518		let when = match when {519			DispatchTime::At(x) => x,520			// The current block has already completed it's scheduled tasks, so521			// Schedule the task at lest one block after this current block.522			DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),523		};524525		if when <= now {526			return Err(Error::<T>::TargetBlockNumberInPast.into());527		}528529		Ok(when)530	}531532	fn do_schedule(533		when: DispatchTime<T::BlockNumber>,534		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,535		priority: schedule::Priority,536		origin: T::PalletsOrigin,537		call: <T as Config>::Call,538	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {539		let when = Self::resolve_time(when)?;540541		// sanitize maybe_periodic542		let maybe_periodic = maybe_periodic543			.filter(|p| p.1 > 1 && !p.0.is_zero())544			// Remove one from the number of repetitions since we will schedule one now.545			.map(|(p, c)| (p, c - 1));546		let s = Some(Scheduled {547			maybe_id: None,548			priority,549			call,550			maybe_periodic,551			origin,552			_phantom: PhantomData::<T::AccountId>::default(),553		});554		Agenda::<T>::append(when, s);555		let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;556		if index > T::MaxScheduledPerBlock::get() {557			log::warn!(558				target: "runtime::scheduler",559				"Warning: There are more items queued in the Scheduler than \560				expected from the runtime configuration. An update might be needed.",561			);562		}563		Self::deposit_event(RawEvent::Scheduled(when, index));564565		Ok((when, index))566	}567568	fn do_cancel(569		origin: Option<T::PalletsOrigin>,570		(when, index): TaskAddress<T::BlockNumber>,571	) -> Result<(), DispatchError> {572		let scheduled = Agenda::<T>::try_mutate(when, |agenda| {573			agenda.get_mut(index as usize).map_or(574				Ok(None),575				|s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {576					if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {577						if *o != s.origin {578							return Err(BadOrigin.into());579						}580					};581					Ok(s.take())582				},583			)584		})?;585		if let Some(s) = scheduled {586			if let Some(id) = s.maybe_id {587				Lookup::<T>::remove(id);588			}589			Self::deposit_event(RawEvent::Canceled(when, index));590			Ok(())591		} else {592			Err(Error::<T>::NotFound.into())593		}594	}595596	fn do_reschedule(597		(when, index): TaskAddress<T::BlockNumber>,598		new_time: DispatchTime<T::BlockNumber>,599	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {600		let new_time = Self::resolve_time(new_time)?;601602		if new_time == when {603			return Err(Error::<T>::RescheduleNoChange.into());604		}605606		Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {607			let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;608			let task = task.take().ok_or(Error::<T>::NotFound)?;609			Agenda::<T>::append(new_time, Some(task));610			Ok(())611		})?;612613		let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;614		Self::deposit_event(RawEvent::Canceled(when, index));615		Self::deposit_event(RawEvent::Scheduled(new_time, new_index));616617		Ok((new_time, new_index))618	}619620	fn do_schedule_named(621		id: Vec<u8>,622		when: DispatchTime<T::BlockNumber>,623		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,624		priority: schedule::Priority,625		origin: T::PalletsOrigin,626		call: <T as Config>::Call,627	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {628		// ensure id it is unique629		if Lookup::<T>::contains_key(&id) {630			return Err(Error::<T>::FailedToSchedule.into());631		}632633		let when = Self::resolve_time(when)?;634635		// sanitize maybe_periodic636		let maybe_periodic = maybe_periodic637			.filter(|p| p.1 > 1 && !p.0.is_zero())638			// Remove one from the number of repetitions since we will schedule one now.639			.map(|(p, c)| (p, c - 1));640641		let s = Scheduled {642			maybe_id: Some(id.clone()),643			priority,644			call,645			maybe_periodic,646			origin,647			_phantom: Default::default(),648		};649		Agenda::<T>::append(when, Some(s));650		let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;651		if index > T::MaxScheduledPerBlock::get() {652			log::warn!(653				target: "runtime::scheduler",654				"Warning: There are more items queued in the Scheduler than \655				expected from the runtime configuration. An update might be needed.",656			);657		}658		let address = (when, index);659		Lookup::<T>::insert(&id, &address);660		Self::deposit_event(RawEvent::Scheduled(when, index));661662		Ok(address)663	}664665	fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: Vec<u8>) -> DispatchResult {666		Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {667			if let Some((when, index)) = lookup.take() {668				let i = index as usize;669				Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {670					if let Some(s) = agenda.get_mut(i) {671						if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {672							if *o != s.origin {673								return Err(BadOrigin.into());674							}675						}676						*s = None;677					}678					Ok(())679				})?;680				Self::deposit_event(RawEvent::Canceled(when, index));681				Ok(())682			} else {683				Err(Error::<T>::NotFound.into())684			}685		})686	}687688	fn do_reschedule_named(689		id: Vec<u8>,690		new_time: DispatchTime<T::BlockNumber>,691	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {692		let new_time = Self::resolve_time(new_time)?;693694		Lookup::<T>::try_mutate_exists(695			id,696			|lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {697				let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;698699				if new_time == when {700					return Err(Error::<T>::RescheduleNoChange.into());701				}702703				Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {704					let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;705					let task = task.take().ok_or(Error::<T>::NotFound)?;706					Agenda::<T>::append(new_time, Some(task));707708					Ok(())709				})?;710711				let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;712				Self::deposit_event(RawEvent::Canceled(when, index));713				Self::deposit_event(RawEvent::Scheduled(new_time, new_index));714715				*lookup = Some((new_time, new_index));716717				Ok((new_time, new_index))718			},719		)720	}721}722723impl<T: Config> schedule::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>724	for Module<T>725{726	type Address = TaskAddress<T::BlockNumber>;727728	fn schedule(729		when: DispatchTime<T::BlockNumber>,730		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,731		priority: schedule::Priority,732		origin: T::PalletsOrigin,733		call: <T as Config>::Call,734	) -> Result<Self::Address, DispatchError> {735		Self::do_schedule(when, maybe_periodic, priority, origin, call)736	}737738	fn cancel((when, index): Self::Address) -> Result<(), ()> {739		Self::do_cancel(None, (when, index)).map_err(|_| ())740	}741742	fn reschedule(743		address: Self::Address,744		when: DispatchTime<T::BlockNumber>,745	) -> Result<Self::Address, DispatchError> {746		Self::do_reschedule(address, when)747	}748749	fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {750		Agenda::<T>::get(when)751			.get(index as usize)752			.ok_or(())753			.map(|_| when)754	}755}756757impl<T: Config> schedule::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>758	for Module<T>759{760	type Address = TaskAddress<T::BlockNumber>;761762	fn schedule_named(763		id: Vec<u8>,764		when: DispatchTime<T::BlockNumber>,765		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,766		priority: schedule::Priority,767		origin: T::PalletsOrigin,768		call: <T as Config>::Call,769	) -> Result<Self::Address, ()> {770		Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ())771	}772773	fn cancel_named(id: Vec<u8>) -> Result<(), ()> {774		Self::do_cancel_named(None, id).map_err(|_| ())775	}776777	fn reschedule_named(778		id: Vec<u8>,779		when: DispatchTime<T::BlockNumber>,780	) -> Result<Self::Address, DispatchError> {781		Self::do_reschedule_named(id, when)782	}783784	fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {785		Lookup::<T>::get(id)786			.and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))787			.ok_or(())788	}789}790791#[cfg(test)]792#[allow(clippy::from_over_into)]793mod tests {794	use super::*;795796	use frame_support::{797		parameter_types, assert_ok, ord_parameter_types, assert_noop, assert_err, Hashable,798		traits::{OnInitialize, OnFinalize, Filter},799		weights::constants::RocksDbWeight,800	};801	use sp_core::H256;802	use sp_runtime::{803		Perbill,804		testing::Header,805		traits::{BlakeTwo256, IdentityLookup},806	};807	use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy};808	use substrate_test_utils::assert_eq_uvec;809	use crate as scheduler;810811	mod logger {812		use super::*;813		use std::cell::RefCell;814815		thread_local! {816			static LOG: RefCell<Vec<(OriginCaller, u32)>> = RefCell::new(Vec::new());817		}818		pub fn log() -> Vec<(OriginCaller, u32)> {819			LOG.with(|log| log.borrow().clone())820		}821		pub trait Config: system::Config {822			type Event: From<Event> + Into<<Self as system::Config>::Event>;823		}824		decl_event! {825			pub enum Event {826				Logged(u32, Weight),827			}828		}829		decl_module! {830			pub struct Module<T: Config> for enum Call831			where832				origin: <T as system::Config>::Origin,833				<T as system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>834			{835				fn deposit_event() = default;836837				#[weight = *weight]838				fn log(origin, i: u32, weight: Weight) {839					Self::deposit_event(Event::Logged(i, weight));840					LOG.with(|log| {841						log.borrow_mut().push((origin.caller().clone(), i));842					})843				}844845				#[weight = *weight]846				fn log_without_filter(origin, i: u32, weight: Weight) {847					Self::deposit_event(Event::Logged(i, weight));848					LOG.with(|log| {849						log.borrow_mut().push((origin.caller().clone(), i));850					})851				}852			}853		}854	}855856	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;857	type Block = frame_system::mocking::MockBlock<Test>;858859	frame_support::construct_runtime!(860		pub enum Test where861			Block = Block,862			NodeBlock = Block,863			UncheckedExtrinsic = UncheckedExtrinsic,864		{865			System: frame_system::{Pallet, Call, Config, Storage, Event<T>},866			Logger: logger::{Pallet, Call, Event},867			Scheduler: scheduler::{Pallet, Call, Storage, Event<T>},868		}869	);870871	// Scheduler must dispatch with root and no filter, this tests base filter is indeed not used.872	pub struct BaseFilter;873	impl Filter<Call> for BaseFilter {874		fn filter(call: &Call) -> bool {875			!matches!(call, Call::Logger(logger::Call::log(_, _)))876		}877	}878879	parameter_types! {880		pub const BlockHashCount: u64 = 250;881		pub BlockWeights: frame_system::limits::BlockWeights =882			frame_system::limits::BlockWeights::simple_max(2_000_000_000_000);883	}884	impl system::Config for Test {885		type BaseCallFilter = BaseFilter;886		type BlockWeights = ();887		type BlockLength = ();888		type DbWeight = RocksDbWeight;889		type Origin = Origin;890		type Call = Call;891		type Index = u64;892		type BlockNumber = u64;893		type Hash = H256;894		type Hashing = BlakeTwo256;895		type AccountId = u64;896		type Lookup = IdentityLookup<Self::AccountId>;897		type Header = Header;898		type Event = Event;899		type BlockHashCount = BlockHashCount;900		type Version = ();901		type PalletInfo = PalletInfo;902		type AccountData = ();903		type OnNewAccount = ();904		type OnKilledAccount = ();905		type SystemWeightInfo = ();906		type SS58Prefix = ();907		type OnSetCode = ();908	}909	impl logger::Config for Test {910		type Event = Event;911	}912	parameter_types! {913		pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;914		pub const MaxScheduledPerBlock: u32 = 10;915	}916	ord_parameter_types! {917		pub const One: u64 = 1;918	}919920	impl Config for Test {921		type Event = Event;922		type Origin = Origin;923		type PalletsOrigin = OriginCaller;924		type Call = Call;925		type MaximumWeight = MaximumSchedulerWeight;926		type ScheduleOrigin = EnsureOneOf<u64, EnsureRoot<u64>, EnsureSignedBy<One, u64>>;927		type MaxScheduledPerBlock = MaxScheduledPerBlock;928		type WeightInfo = ();929		type SponsorshipHandler = ();930	}931932	pub fn new_test_ext() -> sp_io::TestExternalities {933		let t = system::GenesisConfig::default()934			.build_storage::<Test>()935			.unwrap();936		t.into()937	}938939	fn run_to_block(n: u64) {940		while System::block_number() < n {941			Scheduler::on_finalize(System::block_number());942			System::set_block_number(System::block_number() + 1);943			Scheduler::on_initialize(System::block_number());944		}945	}946947	fn root() -> OriginCaller {948		system::RawOrigin::Root.into()949	}950}