git.delta.rocks / unique-network / refs/commits / f2a945f52528

difftreelog

source

pallets/scheduler/src/lib.rs23.9 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// This file is part of Substrate.1920// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.21// SPDX-License-Identifier: Apache-2.02223// Licensed under the Apache License, Version 2.0 (the "License");24// you may not use this file except in compliance with the License.25// You may obtain a copy of the License at26//27// 	<http://www.apache.org/licenses/LICENSE-2.0>28//29// Unless required by applicable law or agreed to in writing, software30// distributed under the License is distributed on an "AS IS" BASIS,31// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.32// See the License for the specific language governing permissions and33// limitations under the License.3435//! # Unique scheduler36//! A Pallet for scheduling dispatches.37//!38//! - [`Config`]39//! - [`Call`]40//! - [`Pallet`]41//!42//! ## Overview43//!44//! This Pallet exposes capabilities for scheduling dispatches to occur at a45//! specified block number or at a specified period. These scheduled dispatches46//! should be named and may be canceled.47//!48//! **NOTE:** The unique scheduler is designed for deferred transaction calls by block number.49//! Any user can book a call of a certain transaction to a specific block number.50//! Also possible to book a call with a certain frequency.51//!52//! Key differences from the original pallet:53//! <https://crates.io/crates/pallet-scheduler>54//! Schedule Id restricted by 16 bytes. Identificator for booked call.55//! Priority limited by HARD DEADLINE (<= 63). Calls over maximum weight don't include to block.56//! The maximum weight that may be scheduled per block for any dispatchables of less priority than `schedule::HARD_DEADLINE`.57//! Maybe_periodic limit is 100 calls. Reserved for future sponsored transaction support.58//! At 100 calls reserved amount is not so much and this is avoid potential problems with balance locks.59//! Any account allowed to schedule any calls. Account withdraw implemented through default transaction logic.60//!61//! ## Interface62//!63//! ### Dispatchable Functions64//!65//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter66//!   that can be used for identification.67//! * `cancel_named` - the named complement to the cancel function.6869// Ensure we're `no_std` when compiling for Wasm.70#![cfg_attr(not(feature = "std"), no_std)]7172#[cfg(feature = "runtime-benchmarks")]73mod benchmarking;7475pub mod weights;7677use sp_core::H160;78use codec::{Codec, Decode, Encode};79use frame_system::{self as system, ensure_signed};80pub use pallet::*;81use scale_info::TypeInfo;82use sp_runtime::{83	traits::{BadOrigin, One, Saturating, Zero},84	RuntimeDebug, DispatchErrorWithPostInfo,85};86use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};8788use frame_support::{89	dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter, GetDispatchInfo},90	traits::{91		schedule::{self, DispatchTime, MaybeHashed},92		NamedReservableCurrency, EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp,93		StorageVersion,94	},95	weights::{Weight},96};9798pub use weights::WeightInfo;99100/// Just a simple index for naming period tasks.101pub type PeriodicIndex = u32;102/// The location of a scheduled task that can be used to remove it.103pub type TaskAddress<BlockNumber> = (BlockNumber, u32);104pub const MAX_TASK_ID_LENGTH_IN_BYTES: u8 = 16;105106type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize];107pub type CallOrHashOf<T> =108	MaybeHashed<<T as Config>::RuntimeCall, <T as frame_system::Config>::Hash>;109110/// Information regarding an item to be executed in the future.111#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]112#[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)]113pub struct ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId> {114	/// The unique identity for this task, if there is one.115	maybe_id: Option<ScheduledId>,116	/// This task's priority.117	priority: schedule::Priority,118	/// The call to be dispatched.119	call: Call,120	/// If the call is periodic, then this points to the information concerning that.121	maybe_periodic: Option<schedule::Period<BlockNumber>>,122	/// The origin to dispatch the call.123	origin: PalletsOrigin,124	_phantom: PhantomData<AccountId>,125}126127pub type ScheduledV3Of<T> = ScheduledV3<128	CallOrHashOf<T>,129	<T as frame_system::Config>::BlockNumber,130	<T as Config>::PalletsOrigin,131	<T as frame_system::Config>::AccountId,132>;133134pub type ScheduledOf<T> = ScheduledV3Of<T>;135136/// The current version of Scheduled struct.137pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =138	ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId>;139140#[cfg(feature = "runtime-benchmarks")]141mod preimage_provider {142	use frame_support::traits::PreimageRecipient;143	pub trait PreimageProviderAndMaybeRecipient<H>: PreimageRecipient<H> {}144	impl<H, T: PreimageRecipient<H>> PreimageProviderAndMaybeRecipient<H> for T {}145}146147#[cfg(not(feature = "runtime-benchmarks"))]148mod preimage_provider {149	use frame_support::traits::PreimageProvider;150	pub trait PreimageProviderAndMaybeRecipient<H>: PreimageProvider<H> {}151	impl<H, T: PreimageProvider<H>> PreimageProviderAndMaybeRecipient<H> for T {}152}153154pub use preimage_provider::PreimageProviderAndMaybeRecipient;155156pub(crate) trait MarginalWeightInfo: WeightInfo {157	fn item(periodic: bool, named: bool, resolved: Option<bool>) -> Weight {158		match (periodic, named, resolved) {159			(_, false, None) => Self::on_initialize_aborted(2) - Self::on_initialize_aborted(1),160			(_, true, None) => {161				Self::on_initialize_named_aborted(2) - Self::on_initialize_named_aborted(1)162			}163			(false, false, Some(false)) => Self::on_initialize(2) - Self::on_initialize(1),164			(false, true, Some(false)) => {165				Self::on_initialize_named(2) - Self::on_initialize_named(1)166			}167			(true, false, Some(false)) => {168				Self::on_initialize_periodic(2) - Self::on_initialize_periodic(1)169			}170			(true, true, Some(false)) => {171				Self::on_initialize_periodic_named_resolved(2)172					- Self::on_initialize_periodic_named_resolved(1)173			}174			(false, false, Some(true)) => Self::on_initialize(2) - Self::on_initialize(1),175			(false, true, Some(true)) => {176				Self::on_initialize_named_resolved(2) - Self::on_initialize_named_resolved(1)177			}178			(true, false, Some(true)) => {179				Self::on_initialize_periodic_resolved(2) - Self::on_initialize_periodic_resolved(1)180			}181			(true, true, Some(true)) => {182				Self::on_initialize_periodic_named_resolved(2)183					- Self::on_initialize_periodic_named_resolved(1)184			}185		}186	}187}188impl<T: WeightInfo> MarginalWeightInfo for T {}189190#[frame_support::pallet]191pub mod pallet {192	use super::*;193	use frame_support::{194		dispatch::PostDispatchInfo,195		pallet_prelude::*,196		traits::{schedule::LookupError, PreimageProvider},197	};198	use frame_system::pallet_prelude::*;199200	/// The current storage version.201	const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);202203	#[pallet::pallet]204	#[pallet::generate_store(pub(super) trait Store)]205	#[pallet::storage_version(STORAGE_VERSION)]206	#[pallet::without_storage_info]207	pub struct Pallet<T>(_);208209	/// `system::Config` should always be included in our implied traits.210	#[pallet::config]211	pub trait Config: frame_system::Config {212		/// The overarching event type.213		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;214215		/// The aggregated origin which the dispatch will take.216		type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>217			+ From<Self::PalletsOrigin>218			+ IsType<<Self as system::Config>::RuntimeOrigin>;219220		/// The caller origin, overarching type of all pallets origins.221		type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq + TypeInfo;222223		type Currency: NamedReservableCurrency<Self::AccountId, ReserveIdentifier = ScheduledId>;224225		/// The aggregated call type.226		type RuntimeCall: Parameter227			+ Dispatchable<228				RuntimeOrigin = <Self as Config>::RuntimeOrigin,229				PostInfo = PostDispatchInfo,230			> + GetDispatchInfo231			+ From<system::Call<Self>>;232233		/// The maximum weight that may be scheduled per block for any dispatchables of less234		/// priority than `schedule::HARD_DEADLINE`.235		#[pallet::constant]236		type MaximumWeight: Get<Weight>;237238		/// Required origin to schedule or cancel calls.239		type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::RuntimeOrigin>;240241		/// Compare the privileges of origins.242		///243		/// This will be used when canceling a task, to ensure that the origin that tries244		/// to cancel has greater or equal privileges as the origin that created the scheduled task.245		///246		/// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can247		/// be used. This will only check if two given origins are equal.248		type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;249250		/// The maximum number of scheduled calls in the queue for a single block.251		/// Not strictly enforced, but used for weight estimation.252		#[pallet::constant]253		type MaxScheduledPerBlock: Get<u32>;254255		/// Weight information for extrinsics in this pallet.256		type WeightInfo: WeightInfo;257258		/// The preimage provider with which we look up call hashes to get the call.259		type PreimageProvider: PreimageProviderAndMaybeRecipient<Self::Hash>;260261		/// If `Some` then the number of blocks to postpone execution for when the item is delayed.262		type NoPreimagePostponement: Get<Option<Self::BlockNumber>>;263264		/// Sponsoring function.265		// type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;266267		/// The helper type used for custom transaction fee logic.268		type CallExecutor: DispatchCall<Self, H160>;269	}270271	/// A Scheduler-Runtime interface for finer payment handling.272	pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {273		/// Reserve (lock) the maximum spendings on a call, calculated from its weight and the repetition count.274		fn reserve_balance(275			id: ScheduledId,276			sponsor: <T as frame_system::Config>::AccountId,277			call: <T as Config>::RuntimeCall,278			count: u32,279		) -> Result<(), DispatchError>;280281		/// Unreserve (unlock) a certain amount from the payer's reserved funds, returning the change.282		fn pay_for_call(283			id: ScheduledId,284			sponsor: <T as frame_system::Config>::AccountId,285			call: <T as Config>::RuntimeCall,286		) -> Result<u128, DispatchError>;287288		/// Resolve the call dispatch, including any post-dispatch operations.289		fn dispatch_call(290			signer: T::AccountId,291			function: <T as Config>::RuntimeCall,292		) -> Result<293			Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,294			TransactionValidityError,295		>;296297		/// Release unspent reserved funds in case of a schedule cancel.298		fn cancel_reserve(299			id: ScheduledId,300			sponsor: <T as frame_system::Config>::AccountId,301		) -> Result<u128, DispatchError>;302	}303304	/// Items to be executed, indexed by the block number that they should be executed on.305	#[pallet::storage]306	pub type Agenda<T: Config> =307		StorageMap<_, Twox64Concat, T::BlockNumber, Vec<Option<ScheduledV3Of<T>>>, ValueQuery>;308309	/// Lookup from identity to the block number and index of the task.310	#[pallet::storage]311	pub(crate) type Lookup<T: Config> =312		StorageMap<_, Twox64Concat, ScheduledId, TaskAddress<T::BlockNumber>>;313314	/// Events type.315	#[pallet::event]316	#[pallet::generate_deposit(pub(super) fn deposit_event)]317	pub enum Event<T: Config> {318		/// Scheduled some task.319		Scheduled { when: T::BlockNumber, index: u32 },320		/// Canceled some task.321		Canceled { when: T::BlockNumber, index: u32 },322		/// Dispatched some task.323		Dispatched {324			task: TaskAddress<T::BlockNumber>,325			id: Option<ScheduledId>,326			result: DispatchResult,327		},328		/// The call for the provided hash was not found so the task has been aborted.329		CallLookupFailed {330			task: TaskAddress<T::BlockNumber>,331			id: Option<ScheduledId>,332			error: LookupError,333		},334	}335336	#[pallet::error]337	pub enum Error<T> {338		/// Failed to schedule a call339		FailedToSchedule,340		/// Cannot find the scheduled call.341		NotFound,342		/// Given target block number is in the past.343		TargetBlockNumberInPast,344		/// Reschedule failed because it does not change scheduled time.345		RescheduleNoChange,346	}347348	#[pallet::hooks]349	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {350		/// Execute the scheduled calls351		fn on_initialize(now: T::BlockNumber) -> Weight {352			let limit = T::MaximumWeight::get();353354			let mut queued = Agenda::<T>::take(now)355				.into_iter()356				.enumerate()357				.filter_map(|(index, s)| Some((index as u32, s?)))358				.collect::<Vec<_>>();359360			if queued.len() as u32 > T::MaxScheduledPerBlock::get() {361				log::warn!(362					target: "runtime::scheduler",363					"Warning: This block has more items queued in Scheduler than \364					expected from the runtime configuration. An update might be needed."365				);366			}367368			queued.sort_by_key(|(_, s)| s.priority);369370			let next = now + One::one();371372			let mut total_weight: Weight = T::WeightInfo::on_initialize(0);373			for (order, (index, mut s)) in queued.into_iter().enumerate() {374				let named = if let Some(ref id) = s.maybe_id {375					Lookup::<T>::remove(id);376					true377				} else {378					false379				};380381				let (call, maybe_completed) = s.call.resolved::<T::PreimageProvider>();382				s.call = call;383384				let resolved = if let Some(completed) = maybe_completed {385					T::PreimageProvider::unrequest_preimage(&completed);386					true387				} else {388					false389				};390				let call = match s.call.as_value().cloned() {391					Some(c) => c,392					None => {393						// Preimage not available - postpone until some block.394						total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));395						if let Some(delay) = T::NoPreimagePostponement::get() {396							let until = now.saturating_add(delay);397							if let Some(ref id) = s.maybe_id {398								let index = Agenda::<T>::decode_len(until).unwrap_or(0);399								Lookup::<T>::insert(id, (until, index as u32));400							}401							Agenda::<T>::append(until, Some(s));402						}403						continue;404					}405				};406407				let periodic = s.maybe_periodic.is_some();408				let call_weight = call.get_dispatch_info().weight;409				let mut item_weight = T::WeightInfo::item(periodic, named, Some(resolved));410				let origin = <<T as Config>::RuntimeOrigin as From<T::PalletsOrigin>>::from(411					s.origin.clone(),412				)413				.into();414				if ensure_signed(origin).is_ok() {415					// Weights of Signed dispatches expect their signing account to be whitelisted.416					item_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));417				}418419				// We allow a scheduled call if any is true:420				// - It's priority is `HARD_DEADLINE`421				// - It does not push the weight past the limit.422				// - It is the first item in the schedule423				let hard_deadline = s.priority <= schedule::HARD_DEADLINE;424				let test_weight = total_weight425					.saturating_add(call_weight)426					.saturating_add(item_weight);427				if !hard_deadline && order > 0 && test_weight.all_gt(limit) {428					// Cannot be scheduled this block - postpone until next.429					total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));430					if let Some(ref id) = s.maybe_id {431						// NOTE: We could reasonably not do this (in which case there would be one432						// block where the named and delayed item could not be referenced by name),433						// but we will do it anyway since it should be mostly free in terms of434						// weight and it is slightly cleaner.435						let index = Agenda::<T>::decode_len(next).unwrap_or(0);436						Lookup::<T>::insert(id, (next, index as u32));437					}438					Agenda::<T>::append(next, Some(s));439					continue;440				}441442				let sender = ensure_signed(443					<<T as Config>::RuntimeOrigin as From<T::PalletsOrigin>>::from(444						s.origin.clone(),445					)446					.into(),447				)448				.unwrap();449450				// // if call have id it was be reserved451				// if s.maybe_id.is_some() {452				// 	let _ = T::CallExecutor::pay_for_call(453				// 		s.maybe_id.unwrap(),454				// 		sender.clone(),455				// 		call.clone(),456				// 	);457				// }458459				// Execute transaction via chain default pipeline460				// That means dispatch will be processed like any user's extrinsic e.g. transaction fees will be taken461				let r = T::CallExecutor::dispatch_call(sender, call.clone());462463				let mut actual_call_weight: Weight = item_weight;464				let result: Result<_, DispatchError> = match r {465					Ok(o) => match o {466						Ok(di) => {467							actual_call_weight = di.actual_weight.unwrap_or(item_weight);468							Ok(())469						}470						Err(err) => Err(err.error),471					},472					Err(_) => {473						log::error!(474							target: "runtime::scheduler",475							"Warning: Scheduler has failed to execute a post-dispatch transaction. \476							This block might have become invalid.");477						Err(DispatchError::CannotLookup)478					} // todo possibly force a skip/return here, do something with the error479				};480481				total_weight.saturating_accrue(item_weight);482				total_weight.saturating_accrue(actual_call_weight);483484				Self::deposit_event(Event::Dispatched {485					task: (now, index),486					id: s.maybe_id.clone(),487					result,488				});489490				if let &Some((period, count)) = &s.maybe_periodic {491					if count > 1 {492						s.maybe_periodic = Some((period, count - 1));493					} else {494						s.maybe_periodic = None;495					}496					let wake = now + period;497					// If scheduled is named, place its information in `Lookup`498					if let Some(ref id) = s.maybe_id {499						let wake_index = Agenda::<T>::decode_len(wake).unwrap_or(0);500						Lookup::<T>::insert(id, (wake, wake_index as u32));501					}502					Agenda::<T>::append(wake, Some(s));503				}504			}505			// Total weight should be 0, because the transaction is already paid for506			Weight::zero()507		}508	}509510	#[pallet::call]511	impl<T: Config> Pallet<T> {512		/// Schedule a named task.513		#[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]514		pub fn schedule_named(515			origin: OriginFor<T>,516			id: ScheduledId,517			when: T::BlockNumber,518			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,519			priority: schedule::Priority,520			call: Box<CallOrHashOf<T>>,521		) -> DispatchResult {522			T::ScheduleOrigin::ensure_origin(origin.clone())?;523			let origin = <T as Config>::RuntimeOrigin::from(origin);524			Self::do_schedule_named(525				id,526				DispatchTime::At(when),527				maybe_periodic,528				priority,529				origin.caller().clone(),530				*call,531			)?;532			Ok(())533		}534535		/// Cancel a named scheduled task.536		#[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]537		pub fn cancel_named(origin: OriginFor<T>, id: ScheduledId) -> DispatchResult {538			T::ScheduleOrigin::ensure_origin(origin.clone())?;539			let origin = <T as Config>::RuntimeOrigin::from(origin);540			Self::do_cancel_named(Some(origin.caller().clone()), id)?;541			Ok(())542		}543544		/// Schedule a named task after a delay.545		///546		/// # <weight>547		/// Same as [`schedule_named`](Self::schedule_named).548		/// # </weight>549		#[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]550		pub fn schedule_named_after(551			origin: OriginFor<T>,552			id: ScheduledId,553			after: T::BlockNumber,554			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,555			priority: schedule::Priority,556			call: Box<CallOrHashOf<T>>,557		) -> DispatchResult {558			T::ScheduleOrigin::ensure_origin(origin.clone())?;559			let origin = <T as Config>::RuntimeOrigin::from(origin);560			Self::do_schedule_named(561				id,562				DispatchTime::After(after),563				maybe_periodic,564				priority,565				origin.caller().clone(),566				*call,567			)?;568			Ok(())569		}570	}571}572573impl<T: Config> Pallet<T> {574	#[cfg(feature = "try-runtime")]575	pub fn pre_migrate_to_v3() -> Result<(), &'static str> {576		Ok(())577	}578579	#[cfg(feature = "try-runtime")]580	pub fn post_migrate_to_v3() -> Result<(), &'static str> {581		use frame_support::dispatch::GetStorageVersion;582583		assert!(Self::current_storage_version() == 3);584		for k in Agenda::<T>::iter_keys() {585			let _ = Agenda::<T>::try_get(k).map_err(|()| "Invalid item in Agenda")?;586		}587		Ok(())588	}589590	/// Helper to migrate scheduler when the pallet origin type has changed.591	pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {592		Agenda::<T>::translate::<593			Vec<Option<Scheduled<CallOrHashOf<T>, T::BlockNumber, OldOrigin, T::AccountId>>>,594			_,595		>(|_, agenda| {596			Some(597				agenda598					.into_iter()599					.map(|schedule| {600						schedule.map(|schedule| Scheduled {601							maybe_id: schedule.maybe_id,602							priority: schedule.priority,603							call: schedule.call,604							maybe_periodic: schedule.maybe_periodic,605							origin: schedule.origin.into(),606							_phantom: Default::default(),607						})608					})609					.collect::<Vec<_>>(),610			)611		});612	}613614	fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {615		let now = frame_system::Pallet::<T>::block_number();616617		let when = match when {618			DispatchTime::At(x) => x,619			// The current block has already completed it's scheduled tasks, so620			// Schedule the task at lest one block after this current block.621			DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),622		};623624		if when <= now {625			return Err(Error::<T>::TargetBlockNumberInPast.into());626		}627628		Ok(when)629	}630631	fn do_schedule_named(632		id: ScheduledId,633		when: DispatchTime<T::BlockNumber>,634		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,635		priority: schedule::Priority,636		origin: T::PalletsOrigin,637		call: CallOrHashOf<T>,638	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {639		// ensure id it is unique640		if Lookup::<T>::contains_key(&id) {641			return Err(Error::<T>::FailedToSchedule)?;642		}643644		let when = Self::resolve_time(when)?;645646		call.ensure_requested::<T::PreimageProvider>();647648		// sanitize maybe_periodic649		let maybe_periodic = maybe_periodic650			.filter(|p| p.1 > 1 && !p.0.is_zero())651			// Remove one from the number of repetitions since we will schedule one now.652			.map(|(p, c)| (p, c - 1));653654		let s = Scheduled {655			maybe_id: Some(id.clone()),656			priority,657			call: call.clone(),658			maybe_periodic,659			origin: origin.clone(),660			_phantom: Default::default(),661		};662663		// reserve balance for periodic execution664		// let sender =665		// 	ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;666		// let repeats = match maybe_periodic {667		// 	Some(p) => p.1,668		// 	None => 1,669		// };670		// let _ = T::CallExecutor::reserve_balance(671		// 	id.clone(),672		// 	sender,673		// 	call.as_value().unwrap().clone(),674		// 	repeats,675		// );676677		Agenda::<T>::append(when, Some(s));678		let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;679		let address = (when, index);680		Lookup::<T>::insert(&id, &address);681		Self::deposit_event(Event::Scheduled { when, index });682683		Ok(address)684	}685686	fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: ScheduledId) -> DispatchResult {687		Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {688			if let Some((when, index)) = lookup.take() {689				let i = index as usize;690				Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {691					if let Some(s) = agenda.get_mut(i) {692						if let (Some(ref o), Some(ref s)) = (origin.clone(), s.borrow()) {693							if matches!(694								T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),695								Some(Ordering::Less) | None696							) {697								return Err(BadOrigin.into());698							}699							// release balance reserve700							// let sender = ensure_signed(701							// 	<<T as Config>::Origin as From<T::PalletsOrigin>>::from(702							// 		origin.unwrap(),703							// 	)704							// 	.into(),705							// )?;706							// let _ = T::CallExecutor::cancel_reserve(id, sender);707708							s.call.ensure_unrequested::<T::PreimageProvider>();709						}710						*s = None;711					}712					Ok(())713				})?;714715				Self::deposit_event(Event::Canceled { when, index });716				Ok(())717			} else {718				Err(Error::<T>::NotFound)?719			}720		})721	}722}