git.delta.rocks / unique-network / refs/commits / 0f7bb4adb180

difftreelog

Merge pull request #341 from UniqueNetwork/feature/simple-scheduler

kozyrevdev2022-06-03parents: #93f5452 #9e5b125.patch.diff
in: master
Feature/simple scheduler

11 files changed

modifiedpallets/scheduler/Cargo.tomldiffbeforeafterboth
--- a/pallets/scheduler/Cargo.toml
+++ b/pallets/scheduler/Cargo.toml
@@ -15,11 +15,13 @@
 scale-info = { version = "2.0.1", default-features = false, features = [
 	"derive",
 ] }
+
 frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
 frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
 sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
 sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
 sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
+sp-core = { default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.22' }
 frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
 
 up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.22" }
@@ -40,6 +42,7 @@
 	"up-sponsorship/std",
 	"sp-io/std",
 	"sp-std/std",
+	"sp-core/std",
 	"log/std",
 ]
 runtime-benchmarks = [
modifiedpallets/scheduler/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -1,23 +1,6 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-// Original license
 // This file is part of Substrate.
 
-// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,16 +15,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! # Scheduler
-//! A module for scheduling dispatches.
-//!
-//! - [`Config`]
-//! - [`Call`]
-//! - [`Module`]
-//!
-//! ## Overview
+//! # Schedulerdo_reschedule
 //!
-//! This module exposes capabilities for scheduling dispatches to occur at a
+//! This Pallet exposes capabilities for scheduling dispatches to occur at a
 //! specified block number or at a specified period. These scheduled dispatches
 //! may be named or anonymous and may be canceled.
 //!
@@ -57,106 +33,59 @@
 //!
 //! ### Dispatchable Functions
 //!
-//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a
-//!   specified block and with a specified priority.
-//! * `cancel` - cancel a scheduled dispatch, specified by block number and
-//!   index.
-//! * `schedule_named` - augments the `schedule` interface with an additional
-//!   `Vec<u8>` parameter that can be used for identification.
+//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and
+//!   with a specified priority.
+//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.
+//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter
+//!   that can be used for identification.
 //! * `cancel_named` - the named complement to the cancel function.
 
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
-#![allow(clippy::type_complexity, clippy::boxed_local, clippy::unused_unit)]
 
+#[cfg(feature = "runtime-benchmarks")]
 mod benchmarking;
+
 pub mod weights;
 
-use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow};
-use codec::{Encode, Decode, Codec};
+use sp_core::H160;
+use codec::{Codec, Decode, Encode};
+use frame_system::{self as system, ensure_signed};
+pub use pallet::*;
+use scale_info::TypeInfo;
 use sp_runtime::{
-	RuntimeDebug,
-	traits::{Zero, One, BadOrigin, Saturating},
+	traits::{BadOrigin, One, Saturating, Zero},
+	RuntimeDebug, DispatchErrorWithPostInfo,
 };
+use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};
+
 use frame_support::{
-	decl_module, decl_storage, decl_event, decl_error,
-	dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter},
+	dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter},
 	traits::{
-		Get,
-		schedule::{self, DispatchTime},
-		OriginTrait, EnsureOrigin, IsType,
+		schedule::{self, DispatchTime, MaybeHashed},
+		NamedReservableCurrency, EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp,
+		StorageVersion,
 	},
 	weights::{GetDispatchInfo, Weight},
 };
-use frame_system::{self as system, ensure_signed};
-pub use weights::WeightInfo;
-use up_sponsorship::SponsorshipHandler;
-use scale_info::TypeInfo;
 
-/// Our pallet's configuration trait. All our types and constants go in here. If the
-/// pallet is dependent on specific other pallets, then their configuration traits
-/// should be added to our implied traits list.
-///
-/// `system::Config` should always be included in our implied traits.
-/// //
-pub trait Config: system::Config {
-	/// The overarching event type.
-	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;
-
-	/// The aggregated origin which the dispatch will take.
-	type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
-		+ From<Self::PalletsOrigin>
-		+ IsType<<Self as system::Config>::Origin>;
-
-	/// The caller origin, overarching type of all pallets origins.
-	type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + TypeInfo + Clone + Eq;
-
-	/// The aggregated call type.
-	type Call: Parameter
-		+ Dispatchable<Origin = <Self as Config>::Origin>
-		+ GetDispatchInfo
-		+ From<system::Call<Self>>;
-
-	/// The maximum weight that may be scheduled per block for any dispatchables of less priority
-	/// than `schedule::HARD_DEADLINE`.
-	type MaximumWeight: Get<Weight>;
-
-	/// Required origin to schedule or cancel calls.
-	type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;
-
-	/// The maximum number of scheduled calls in the queue for a single block.
-	/// Not strictly enforced, but used for weight estimation.
-	type MaxScheduledPerBlock: Get<u32>;
-
-	/// Sponsoring function
-	type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;
-
-	/// Weight information for extrinsics in this pallet.
-	type WeightInfo: WeightInfo;
-}
+pub use weights::WeightInfo;
 
-// pub type SelfWeightInfo<T> = <T as system::Config>::WeightInfo;
-
 /// Just a simple index for naming period tasks.
 pub type PeriodicIndex = u32;
 /// The location of a scheduled task that can be used to remove it.
 pub type TaskAddress<BlockNumber> = (BlockNumber, u32);
+pub const MAX_TASK_ID_LENGTH_IN_BYTES: u8 = 16;
 
-#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]
-#[derive(Clone, RuntimeDebug, Encode, Decode)]
-struct ScheduledV1<Call, BlockNumber> {
-	maybe_id: Option<Vec<u8>>,
-	priority: schedule::Priority,
-	call: Call,
-	maybe_periodic: Option<schedule::Period<BlockNumber>>,
-}
+type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize];
+pub type CallOrHashOf<T> = MaybeHashed<<T as Config>::Call, <T as frame_system::Config>::Hash>;
 
 /// Information regarding an item to be executed in the future.
 #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]
 #[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)]
-pub struct ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId> {
+pub struct ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId> {
 	/// The unique identity for this task, if there is one.
-	maybe_id: Option<Vec<u8>>,
+	maybe_id: Option<ScheduledId>,
 	/// This task's priority.
 	priority: schedule::Priority,
 	/// The call to be dispatched.
@@ -168,63 +97,213 @@
 	_phantom: PhantomData<AccountId>,
 }
 
+pub type ScheduledV3Of<T> = ScheduledV3<
+	CallOrHashOf<T>,
+	<T as frame_system::Config>::BlockNumber,
+	<T as Config>::PalletsOrigin,
+	<T as frame_system::Config>::AccountId,
+>;
+
+pub type ScheduledOf<T> = ScheduledV3Of<T>;
+
 /// The current version of Scheduled struct.
 pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =
-	ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId>;
+	ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId>;
 
-// A value placed in storage that represents the current version of the Scheduler storage.
-// This value is used by the `on_runtime_upgrade` logic to determine whether we run
-// storage migration logic.
-#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
-enum Releases {
-	V1,
-	V2,
+#[cfg(feature = "runtime-benchmarks")]
+mod preimage_provider {
+	use frame_support::traits::PreimageRecipient;
+	pub trait PreimageProviderAndMaybeRecipient<H>: PreimageRecipient<H> {}
+	impl<H, T: PreimageRecipient<H>> PreimageProviderAndMaybeRecipient<H> for T {}
 }
 
-impl Default for Releases {
-	fn default() -> Self {
-		Releases::V1
-	}
+#[cfg(not(feature = "runtime-benchmarks"))]
+mod preimage_provider {
+	use frame_support::traits::PreimageProvider;
+	pub trait PreimageProviderAndMaybeRecipient<H>: PreimageProvider<H> {}
+	impl<H, T: PreimageProvider<H>> PreimageProviderAndMaybeRecipient<H> for T {}
 }
 
-#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
-pub struct CallSpec {
-	module: u32,
-	method: u32,
+pub use preimage_provider::PreimageProviderAndMaybeRecipient;
+
+pub(crate) trait MarginalWeightInfo: WeightInfo {
+	fn item(periodic: bool, named: bool, resolved: Option<bool>) -> Weight {
+		match (periodic, named, resolved) {
+			(_, false, None) => Self::on_initialize_aborted(2) - Self::on_initialize_aborted(1),
+			(_, true, None) => {
+				Self::on_initialize_named_aborted(2) - Self::on_initialize_named_aborted(1)
+			}
+			(false, false, Some(false)) => Self::on_initialize(2) - Self::on_initialize(1),
+			(false, true, Some(false)) => {
+				Self::on_initialize_named(2) - Self::on_initialize_named(1)
+			}
+			(true, false, Some(false)) => {
+				Self::on_initialize_periodic(2) - Self::on_initialize_periodic(1)
+			}
+			(true, true, Some(false)) => {
+				Self::on_initialize_periodic_named(2) - Self::on_initialize_periodic_named(1)
+			}
+			(false, false, Some(true)) => {
+				Self::on_initialize_resolved(2) - Self::on_initialize_resolved(1)
+			}
+			(false, true, Some(true)) => {
+				Self::on_initialize_named_resolved(2) - Self::on_initialize_named_resolved(1)
+			}
+			(true, false, Some(true)) => {
+				Self::on_initialize_periodic_resolved(2) - Self::on_initialize_periodic_resolved(1)
+			}
+			(true, true, Some(true)) => {
+				Self::on_initialize_periodic_named_resolved(2)
+					- Self::on_initialize_periodic_named_resolved(1)
+			}
+		}
+	}
 }
+impl<T: WeightInfo> MarginalWeightInfo for T {}
 
-decl_storage! {
-	trait Store for Module<T: Config> as Scheduler {
-		/// Items to be executed, indexed by the block number that they should be executed on.
-		pub Agenda: map hasher(twox_64_concat) T::BlockNumber
-			=> Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>;
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{
+		dispatch::PostDispatchInfo,
+		pallet_prelude::*,
+		traits::{schedule::LookupError, PreimageProvider},
+	};
+	use frame_system::pallet_prelude::*;
+
+	/// The current storage version.
+	const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	#[pallet::storage_version(STORAGE_VERSION)]
+	#[pallet::without_storage_info]
+	pub struct Pallet<T>(_);
+
+	/// `system::Config` should always be included in our implied traits.
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// The overarching event type.
+		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+
+		/// The aggregated origin which the dispatch will take.
+		type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
+			+ From<Self::PalletsOrigin>
+			+ IsType<<Self as system::Config>::Origin>;
+
+		/// The caller origin, overarching type of all pallets origins.
+		type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq + TypeInfo;
+
+		type Currency: NamedReservableCurrency<Self::AccountId, ReserveIdentifier = ScheduledId>;
+
+		/// The aggregated call type.
+		type Call: Parameter
+			+ Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>
+			+ GetDispatchInfo
+			+ From<system::Call<Self>>;
 
-		pub SpecAgenda: map hasher(twox_64_concat) T::BlockNumber
-			=> Vec<Option<CallSpec>>;
+		/// The maximum weight that may be scheduled per block for any dispatchables of less
+		/// priority than `schedule::HARD_DEADLINE`.
+		#[pallet::constant]
+		type MaximumWeight: Get<Weight>;
 
-		/// Lookup from identity to the block number and index of the task.
-		Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;
+		/// Required origin to schedule or cancel calls.
+		type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;
 
-		/// Storage version of the pallet.
+		/// Compare the privileges of origins.
+		///
+		/// This will be used when canceling a task, to ensure that the origin that tries
+		/// to cancel has greater or equal privileges as the origin that created the scheduled task.
 		///
-		/// New networks start with last version.
-		StorageVersion build(|_| Releases::V2): Releases;
+		/// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can
+		/// be used. This will only check if two given origins are equal.
+		type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;
+
+		/// The maximum number of scheduled calls in the queue for a single block.
+		/// Not strictly enforced, but used for weight estimation.
+		#[pallet::constant]
+		type MaxScheduledPerBlock: Get<u32>;
+
+		/// Weight information for extrinsics in this pallet.
+		type WeightInfo: WeightInfo;
+
+		/// The preimage provider with which we look up call hashes to get the call.
+		type PreimageProvider: PreimageProviderAndMaybeRecipient<Self::Hash>;
+
+		/// If `Some` then the number of blocks to postpone execution for when the item is delayed.
+		type NoPreimagePostponement: Get<Option<Self::BlockNumber>>;
+
+		/// Sponsoring function.
+		// type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;
+
+		/// The helper type used for custom transaction fee logic.
+		type CallExecutor: DispatchCall<Self, H160>;
 	}
-}
 
-decl_event!(
-	pub enum Event<T> where <T as system::Config>::BlockNumber {
-		/// Scheduled some task. \[when, index\]
-		Scheduled(BlockNumber, u32),
-		/// Canceled some task. \[when, index\]
-		Canceled(BlockNumber, u32),
-		/// Dispatched some task. \[task, id, result\]
-		Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),
+	/// A Scheduler-Runtime interface for finer payment handling.
+	pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {
+		fn reserve_balance(
+			id: ScheduledId,
+			sponsor: <T as frame_system::Config>::AccountId,
+			call: <T as Config>::Call,
+			count: u32,
+		) -> Result<(), DispatchError>;
+
+		fn pay_for_call(
+			id: ScheduledId,
+			sponsor: <T as frame_system::Config>::AccountId,
+			call: <T as Config>::Call,
+		) -> Result<u128, DispatchError>;
+
+		/// Resolve the call dispatch, including any post-dispatch operations.
+		fn dispatch_call(
+			signer: T::AccountId,
+			function: <T as Config>::Call,
+		) -> Result<
+			Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
+			TransactionValidityError,
+		>;
+
+		fn cancel_reserve(
+			id: ScheduledId,
+			sponsor: <T as frame_system::Config>::AccountId,
+		) -> Result<u128, DispatchError>;
 	}
-);
 
-decl_error! {
-	pub enum Error for Module<T: Config> {
+	/// Items to be executed, indexed by the block number that they should be executed on.
+	#[pallet::storage]
+	pub type Agenda<T: Config> =
+		StorageMap<_, Twox64Concat, T::BlockNumber, Vec<Option<ScheduledV3Of<T>>>, ValueQuery>;
+
+	/// Lookup from identity to the block number and index of the task.
+	#[pallet::storage]
+	pub(crate) type Lookup<T: Config> =
+		StorageMap<_, Twox64Concat, ScheduledId, TaskAddress<T::BlockNumber>>;
+
+	/// Events type.
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// Scheduled some task.
+		Scheduled { when: T::BlockNumber, index: u32 },
+		/// Canceled some task.
+		Canceled { when: T::BlockNumber, index: u32 },
+		/// Dispatched some task.
+		Dispatched {
+			task: TaskAddress<T::BlockNumber>,
+			id: Option<ScheduledId>,
+			result: DispatchResult,
+		},
+		/// The call for the provided hash was not found so the task has been aborted.
+		CallLookupFailed {
+			task: TaskAddress<T::BlockNumber>,
+			id: Option<ScheduledId>,
+			error: LookupError,
+		},
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
 		/// Failed to schedule a call
 		FailedToSchedule,
 		/// Cannot find the scheduled call.
@@ -234,249 +313,268 @@
 		/// Reschedule failed because it does not change scheduled time.
 		RescheduleNoChange,
 	}
-}
 
-decl_module! {
-	/// Scheduler module declaration.
-	pub struct Module<T: Config> for enum Call
-	where
-		origin: <T as system::Config>::Origin
-	{
-		type Error = Error<T>;
-		fn deposit_event() = default;
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		/// Execute the scheduled calls
+		fn on_initialize(now: T::BlockNumber) -> Weight {
+			let limit = T::MaximumWeight::get();
 
+			let mut queued = Agenda::<T>::take(now)
+				.into_iter()
+				.enumerate()
+				.filter_map(|(index, s)| Some((index as u32, s?)))
+				.collect::<Vec<_>>();
 
-		/// Anonymously schedule a task.
-		///
-		/// # <weight>
-		/// - S = Number of already scheduled calls
-		/// - Base Weight: 22.29 + .126 * S µs
-		/// - DB Weight:
-		///     - Read: Agenda
-		///     - Write: Agenda
-		/// - Will use base weight of 25 which should be good for up to 30 scheduled calls
-		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
-		fn schedule(origin,
-			when: T::BlockNumber,
-			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
-			priority: schedule::Priority,
-			call: Box<<T as Config>::Call>,
-		)
-		{
-			let origin = <T as Config>::Origin::from(origin);
-			Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?;
-		}
+			if queued.len() as u32 > T::MaxScheduledPerBlock::get() {
+				log::warn!(
+					target: "runtime::scheduler",
+					"Warning: This block has more items queued in Scheduler than \
+					expected from the runtime configuration. An update might be needed."
+				);
+			}
 
-		/// Cancel an anonymously scheduled task.
-		///
-		/// # <weight>
-		/// - S = Number of already scheduled calls
-		/// - Base Weight: 22.15 + 2.869 * S µs
-		/// - DB Weight:
-		///     - Read: Agenda
-		///     - Write: Agenda, Lookup
-		/// - Will use base weight of 100 which should be good for up to 30 scheduled calls
-		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get())]
-		fn cancel(origin, when: T::BlockNumber, index: u32) {
-			T::ScheduleOrigin::ensure_origin(origin.clone())?;
-			let origin = <T as Config>::Origin::from(origin);
-			Self::do_cancel(Some(origin.caller().clone()), (when, index))?;
+			queued.sort_by_key(|(_, s)| s.priority);
+
+			let next = now + One::one();
+
+			let mut total_weight: Weight = T::WeightInfo::on_initialize(0);
+			for (order, (index, mut s)) in queued.into_iter().enumerate() {
+				let named = if let Some(ref id) = s.maybe_id {
+					Lookup::<T>::remove(id);
+					true
+				} else {
+					false
+				};
+
+				let (call, maybe_completed) = s.call.resolved::<T::PreimageProvider>();
+				s.call = call;
+
+				let resolved = if let Some(completed) = maybe_completed {
+					T::PreimageProvider::unrequest_preimage(&completed);
+					true
+				} else {
+					false
+				};
+				let call = match s.call.as_value().cloned() {
+					Some(c) => c,
+					None => {
+						// Preimage not available - postpone until some block.
+						total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));
+						if let Some(delay) = T::NoPreimagePostponement::get() {
+							let until = now.saturating_add(delay);
+							if let Some(ref id) = s.maybe_id {
+								let index = Agenda::<T>::decode_len(until).unwrap_or(0);
+								Lookup::<T>::insert(id, (until, index as u32));
+							}
+							Agenda::<T>::append(until, Some(s));
+						}
+						continue;
+					}
+				};
+
+				let periodic = s.maybe_periodic.is_some();
+				let call_weight = call.get_dispatch_info().weight;
+				let mut item_weight = T::WeightInfo::item(periodic, named, Some(resolved));
+				let origin =
+					<<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())
+						.into();
+				if ensure_signed(origin).is_ok() {
+					// Weights of Signed dispatches expect their signing account to be whitelisted.
+					item_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
+				}
+
+				// We allow a scheduled call if any is true:
+				// - It's priority is `HARD_DEADLINE`
+				// - It does not push the weight past the limit.
+				// - It is the first item in the schedule
+				let hard_deadline = s.priority <= schedule::HARD_DEADLINE;
+				let test_weight = total_weight
+					.saturating_add(call_weight)
+					.saturating_add(item_weight);
+				if !hard_deadline && order > 0 && test_weight > limit {
+					// Cannot be scheduled this block - postpone until next.
+					total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));
+					if let Some(ref id) = s.maybe_id {
+						// NOTE: We could reasonably not do this (in which case there would be one
+						// block where the named and delayed item could not be referenced by name),
+						// but we will do it anyway since it should be mostly free in terms of
+						// weight and it is slightly cleaner.
+						let index = Agenda::<T>::decode_len(next).unwrap_or(0);
+						Lookup::<T>::insert(id, (next, index as u32));
+					}
+					Agenda::<T>::append(next, Some(s));
+					continue;
+				}
+
+				let sender = ensure_signed(
+					<<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())
+						.into(),
+				)
+				.unwrap();
+
+				// // if call have id it was be reserved
+				// if s.maybe_id.is_some() {
+				// 	let _ = T::CallExecutor::pay_for_call(
+				// 		s.maybe_id.unwrap(),
+				// 		sender.clone(),
+				// 		call.clone(),
+				// 	);
+				// }
+
+				let r = T::CallExecutor::dispatch_call(sender, call.clone());
+
+				let mut actual_call_weight: Weight = item_weight;
+				let result: Result<_, DispatchError> = match r {
+					Ok(o) => match o {
+						Ok(di) => {
+							actual_call_weight = di.actual_weight.unwrap_or(item_weight);
+							Ok(())
+						}
+						Err(err) => Err(err.error),
+					},
+					Err(_) => {
+						log::error!(
+							target: "runtime::scheduler",
+							"Warning: Scheduler has failed to execute a post-dispatch transaction. \
+							This block might have become invalid.");
+						Err(DispatchError::CannotLookup)
+					} // todo possibly force a skip/return here, do something with the error
+				};
+
+				total_weight.saturating_accrue(item_weight);
+				total_weight.saturating_accrue(actual_call_weight);
+
+				Self::deposit_event(Event::Dispatched {
+					task: (now, index),
+					id: s.maybe_id.clone(),
+					result,
+				});
+
+				if let &Some((period, count)) = &s.maybe_periodic {
+					if count > 1 {
+						s.maybe_periodic = Some((period, count - 1));
+					} else {
+						s.maybe_periodic = None;
+					}
+					let wake = now + period;
+					// If scheduled is named, place its information in `Lookup`
+					if let Some(ref id) = s.maybe_id {
+						let wake_index = Agenda::<T>::decode_len(wake).unwrap_or(0);
+						Lookup::<T>::insert(id, (wake, wake_index as u32));
+					}
+					Agenda::<T>::append(wake, Some(s));
+				}
+			}
+			0
+			//total_weight
 		}
+	}
 
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
 		/// Schedule a named task.
-		///
-		/// # <weight>
-		/// - S = Number of already scheduled calls
-		/// - Base Weight: 29.6 + .159 * S µs
-		/// - DB Weight:
-		///     - Read: Agenda, Lookup
-		///     - Write: Agenda, Lookup
-		/// - Will use base weight of 35 which should be good for more than 30 scheduled calls
-		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]
-		fn schedule_named(origin,
-			id: Vec<u8>,
+		#[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
+		pub fn schedule_named(
+			origin: OriginFor<T>,
+			id: ScheduledId,
 			when: T::BlockNumber,
 			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
 			priority: schedule::Priority,
-			call: Box<<T as Config>::Call>,
-		) {
+			call: Box<CallOrHashOf<T>>,
+		) -> DispatchResult {
 			T::ScheduleOrigin::ensure_origin(origin.clone())?;
 			let origin = <T as Config>::Origin::from(origin);
 			Self::do_schedule_named(
-				id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call
+				id,
+				DispatchTime::At(when),
+				maybe_periodic,
+				priority,
+				origin.caller().clone(),
+				*call,
 			)?;
+			Ok(())
 		}
 
 		/// Cancel a named scheduled task.
-		///
-		/// # <weight>
-		/// - S = Number of already scheduled calls
-		/// - Base Weight: 24.91 + 2.907 * S µs
-		/// - DB Weight:
-		///     - Read: Agenda, Lookup
-		///     - Write: Agenda, Lookup
-		/// - Will use base weight of 100 which should be good for up to 30 scheduled calls
-		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())]
-		fn cancel_named(origin, id: Vec<u8>) {
+		#[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]
+		pub fn cancel_named(origin: OriginFor<T>, id: ScheduledId) -> DispatchResult {
 			T::ScheduleOrigin::ensure_origin(origin.clone())?;
 			let origin = <T as Config>::Origin::from(origin);
 			Self::do_cancel_named(Some(origin.caller().clone()), id)?;
-		}
-
-		/// Anonymously schedule a task after a delay.
-		///
-		/// # <weight>
-		/// Same as [`schedule`].
-		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
-		fn schedule_after(origin,
-			after: T::BlockNumber,
-			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
-			priority: schedule::Priority,
-			call: Box<<T as Config>::Call>,
-		) {
-			T::ScheduleOrigin::ensure_origin(origin.clone())?;
-			let origin = <T as Config>::Origin::from(origin);
-			Self::do_schedule(
-				DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call
-			)?;
+			Ok(())
 		}
 
 		/// Schedule a named task after a delay.
 		///
 		/// # <weight>
-		/// Same as [`schedule_named`].
+		/// Same as [`schedule_named`](Self::schedule_named).
 		/// # </weight>
-		#[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]
-		fn schedule_named_after(origin,
-			id: Vec<u8>,
+		#[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]
+		pub fn schedule_named_after(
+			origin: OriginFor<T>,
+			id: ScheduledId,
 			after: T::BlockNumber,
 			maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
 			priority: schedule::Priority,
-			call: Box<<T as Config>::Call>,
-		) {
+			call: Box<CallOrHashOf<T>>,
+		) -> DispatchResult {
 			T::ScheduleOrigin::ensure_origin(origin.clone())?;
 			let origin = <T as Config>::Origin::from(origin);
 			Self::do_schedule_named(
-				id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call
+				id,
+				DispatchTime::After(after),
+				maybe_periodic,
+				priority,
+				origin.caller().clone(),
+				*call,
 			)?;
+			Ok(())
 		}
-
-		/// Execute the scheduled calls
-		///
-		/// # <weight>
-		/// - S = Number of already scheduled calls
-		/// - N = Named scheduled calls
-		/// - P = Periodic Calls
-		/// - Base Weight: 9.243 + 23.45 * S µs
-		/// - DB Weight:
-		///     - Read: Agenda + Lookup * N + Agenda(Future) * P
-		///     - Write: Agenda + Lookup * N  + Agenda(future) * P
-		/// # </weight>
-		fn on_initialize(now: T::BlockNumber) -> Weight {
-			let limit = T::MaximumWeight::get();
-			let mut queued = Agenda::<T>::take(now).into_iter()
-				.enumerate()
-				.filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))
-				.collect::<Vec<_>>();
-			if queued.len() as u32 > T::MaxScheduledPerBlock::get() {
-				log::warn!(
-					target: "runtime::scheduler",
-					"Warning: This block has more items queued in Scheduler than \
-					expected from the runtime configuration. An update might be needed."
-				);
-			}
-			queued.sort_by_key(|(_, s)| s.priority);
-			let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)
-			let mut total_weight: Weight = 0;
-			queued.into_iter()
-				.enumerate()
-				.scan(base_weight, |cumulative_weight, (order, (index, s))| {
-					*cumulative_weight = cumulative_weight
-						.saturating_add(s.call.get_dispatch_info().weight);
-
-					let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
-						s.origin.clone()
-					).into();
-
-					if ensure_signed(origin).is_ok() {
-						 // AccountData for inner call origin accountdata.
-						*cumulative_weight = cumulative_weight
-							.saturating_add(T::DbWeight::get().reads_writes(1, 1));
-					}
-
-					if s.maybe_id.is_some() {
-						// Remove/Modify Lookup
-						*cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1));
-					}
-					if s.maybe_periodic.is_some() {
-						// Read/Write Agenda for future block
-						*cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
-					}
+	}
+}
 
-					Some((order, index, *cumulative_weight, s))
-				})
-				.filter_map(|(order, index, cumulative_weight, mut s)| {
-					// We allow a scheduled call if any is true:
-					// - It's priority is `HARD_DEADLINE`
-					// - It does not push the weight past the limit.
-					// - It is the first item in the schedule
-					if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 {
+impl<T: Config> Pallet<T> {
+	#[cfg(feature = "try-runtime")]
+	pub fn pre_migrate_to_v3() -> Result<(), &'static str> {
+		Ok(())
+	}
 
-						let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
-							s.origin.clone()
-						).into();
-						let sender = match ensure_signed(origin) {
-							Ok(v) => v,
-							// TODO: Support for unsigned extrinsics?
-							Err(_) => return Some(Some(s))
-						};
-						let who_will_pay = T::SponsorshipHandler::get_sponsor(&sender, &s.call).unwrap_or(sender);
-						let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));
-						let r = s.call.clone().dispatch(sponsor.into());
-						let maybe_id = s.maybe_id.clone();
-						if let Some((period, count)) = s.maybe_periodic {
-							if count > 1 {
-								s.maybe_periodic = Some((period, count - 1));
-							} else {
-								s.maybe_periodic = None;
-							}
-							let next = now + period;
-							// If scheduled is named, place it's information in `Lookup`
-							if let Some(ref id) = s.maybe_id {
-								let next_index = Agenda::<T>::decode_len(now + period).unwrap_or(0);
-								Lookup::<T>::insert(id, (next, next_index as u32));
-							}
-							Agenda::<T>::append(next, Some(s));
-						} else if let Some(ref id) = s.maybe_id {
-									  Lookup::<T>::remove(id);
-								  }
-						Self::deposit_event(RawEvent::Dispatched(
-							(now, index),
-							maybe_id,
-							r.map(|_| ()).map_err(|e| e.error)
-						));
-						total_weight = cumulative_weight;
-						None
-					} else {
-						Some(Some(s))
-					}
-				})
-				.for_each(|unused| {
-					let next = now + One::one();
-					Agenda::<T>::append(next, unused);
-				});
+	#[cfg(feature = "try-runtime")]
+	pub fn post_migrate_to_v3() -> Result<(), &'static str> {
+		use frame_support::dispatch::GetStorageVersion;
 
-			total_weight
+		assert!(Self::current_storage_version() == 3);
+		for k in Agenda::<T>::iter_keys() {
+			let _ = Agenda::<T>::try_get(k).map_err(|()| "Invalid item in Agenda")?;
 		}
+		Ok(())
+	}
+
+	/// Helper to migrate scheduler when the pallet origin type has changed.
+	pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {
+		Agenda::<T>::translate::<
+			Vec<Option<Scheduled<CallOrHashOf<T>, T::BlockNumber, OldOrigin, T::AccountId>>>,
+			_,
+		>(|_, agenda| {
+			Some(
+				agenda
+					.into_iter()
+					.map(|schedule| {
+						schedule.map(|schedule| Scheduled {
+							maybe_id: schedule.maybe_id,
+							priority: schedule.priority,
+							call: schedule.call,
+							maybe_periodic: schedule.maybe_periodic,
+							origin: schedule.origin.into(),
+							_phantom: Default::default(),
+						})
+					})
+					.collect::<Vec<_>>(),
+			)
+		});
 	}
-}
 
-impl<T: Config> Module<T> {
 	fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {
 		let now = frame_system::Pallet::<T>::block_number();
 
@@ -499,9 +597,10 @@
 		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
 		priority: schedule::Priority,
 		origin: T::PalletsOrigin,
-		call: <T as Config>::Call,
+		call: CallOrHashOf<T>,
 	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
 		let when = Self::resolve_time(when)?;
+		call.ensure_requested::<T::PreimageProvider>();
 
 		// sanitize maybe_periodic
 		let maybe_periodic = maybe_periodic
@@ -518,14 +617,7 @@
 		});
 		Agenda::<T>::append(when, s);
 		let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
-		if index > T::MaxScheduledPerBlock::get() {
-			log::warn!(
-				target: "runtime::scheduler",
-				"Warning: There are more items queued in the Scheduler than \
-				expected from the runtime configuration. An update might be needed.",
-			);
-		}
-		Self::deposit_event(RawEvent::Scheduled(when, index));
+		Self::deposit_event(Event::Scheduled { when, index });
 
 		Ok((when, index))
 	}
@@ -539,7 +631,10 @@
 				Ok(None),
 				|s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {
 					if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
-						if *o != s.origin {
+						if matches!(
+							T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),
+							Some(Ordering::Less) | None
+						) {
 							return Err(BadOrigin.into());
 						}
 					};
@@ -548,13 +643,14 @@
 			)
 		})?;
 		if let Some(s) = scheduled {
+			s.call.ensure_unrequested::<T::PreimageProvider>();
 			if let Some(id) = s.maybe_id {
 				Lookup::<T>::remove(id);
 			}
-			Self::deposit_event(RawEvent::Canceled(when, index));
+			Self::deposit_event(Event::Canceled { when, index });
 			Ok(())
 		} else {
-			Err(Error::<T>::NotFound.into())
+			Err(Error::<T>::NotFound)?
 		}
 	}
 
@@ -576,27 +672,32 @@
 		})?;
 
 		let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;
-		Self::deposit_event(RawEvent::Canceled(when, index));
-		Self::deposit_event(RawEvent::Scheduled(new_time, new_index));
+		Self::deposit_event(Event::Canceled { when, index });
+		Self::deposit_event(Event::Scheduled {
+			when: new_time,
+			index: new_index,
+		});
 
 		Ok((new_time, new_index))
 	}
 
 	fn do_schedule_named(
-		id: Vec<u8>,
+		id: ScheduledId,
 		when: DispatchTime<T::BlockNumber>,
 		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
 		priority: schedule::Priority,
 		origin: T::PalletsOrigin,
-		call: <T as Config>::Call,
+		call: CallOrHashOf<T>,
 	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
 		// ensure id it is unique
 		if Lookup::<T>::contains_key(&id) {
-			return Err(Error::<T>::FailedToSchedule.into());
+			return Err(Error::<T>::FailedToSchedule)?;
 		}
 
 		let when = Self::resolve_time(when)?;
 
+		call.ensure_requested::<T::PreimageProvider>();
+
 		// sanitize maybe_periodic
 		let maybe_periodic = maybe_periodic
 			.filter(|p| p.1 > 1 && !p.0.is_zero())
@@ -606,52 +707,74 @@
 		let s = Scheduled {
 			maybe_id: Some(id.clone()),
 			priority,
-			call,
+			call: call.clone(),
 			maybe_periodic,
-			origin,
+			origin: origin.clone(),
 			_phantom: Default::default(),
 		};
+
+		// reserve balance for periodic execution
+		// let sender =
+		// 	ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;
+		// let repeats = match maybe_periodic {
+		// 	Some(p) => p.1,
+		// 	None => 1,
+		// };
+		// let _ = T::CallExecutor::reserve_balance(
+		// 	id.clone(),
+		// 	sender,
+		// 	call.as_value().unwrap().clone(),
+		// 	repeats,
+		// );
+
 		Agenda::<T>::append(when, Some(s));
 		let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
-		if index > T::MaxScheduledPerBlock::get() {
-			log::warn!(
-				target: "runtime::scheduler",
-				"Warning: There are more items queued in the Scheduler than \
-				expected from the runtime configuration. An update might be needed.",
-			);
-		}
 		let address = (when, index);
 		Lookup::<T>::insert(&id, &address);
-		Self::deposit_event(RawEvent::Scheduled(when, index));
+		Self::deposit_event(Event::Scheduled { when, index });
 
 		Ok(address)
 	}
 
-	fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: Vec<u8>) -> DispatchResult {
+	fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: ScheduledId) -> DispatchResult {
 		Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {
 			if let Some((when, index)) = lookup.take() {
 				let i = index as usize;
 				Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
 					if let Some(s) = agenda.get_mut(i) {
-						if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
-							if *o != s.origin {
+						if let (Some(ref o), Some(ref s)) = (origin.clone(), s.borrow()) {
+							if matches!(
+								T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),
+								Some(Ordering::Less) | None
+							) {
 								return Err(BadOrigin.into());
 							}
+							// release balance reserve
+							// let sender = ensure_signed(
+							// 	<<T as Config>::Origin as From<T::PalletsOrigin>>::from(
+							// 		origin.unwrap(),
+							// 	)
+							// 	.into(),
+							// )?;
+							// let _ = T::CallExecutor::cancel_reserve(id, sender);
+
+							s.call.ensure_unrequested::<T::PreimageProvider>();
 						}
 						*s = None;
 					}
 					Ok(())
 				})?;
-				Self::deposit_event(RawEvent::Canceled(when, index));
+
+				Self::deposit_event(Event::Canceled { when, index });
 				Ok(())
 			} else {
-				Err(Error::<T>::NotFound.into())
+				Err(Error::<T>::NotFound)?
 			}
 		})
 	}
 
 	fn do_reschedule_named(
-		id: Vec<u8>,
+		id: ScheduledId,
 		new_time: DispatchTime<T::BlockNumber>,
 	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
 		let new_time = Self::resolve_time(new_time)?;
@@ -674,8 +797,11 @@
 				})?;
 
 				let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;
-				Self::deposit_event(RawEvent::Canceled(when, index));
-				Self::deposit_event(RawEvent::Scheduled(new_time, new_index));
+				Self::deposit_event(Event::Canceled { when, index });
+				Self::deposit_event(Event::Scheduled {
+					when: new_time,
+					index: new_index,
+				});
 
 				*lookup = Some((new_time, new_index));
 
@@ -684,161 +810,86 @@
 		)
 	}
 }
-
-#[cfg(test)]
-#[allow(clippy::from_over_into)]
-mod tests {
-	use super::*;
 
-	use frame_support::{
-		ord_parameter_types, parameter_types,
-		traits::{Contains, ConstU32, EnsureOneOf},
-		weights::constants::RocksDbWeight,
-	};
-	use sp_core::H256;
-	use sp_runtime::{
-		Perbill,
-		testing::Header,
-		traits::{BlakeTwo256, IdentityLookup},
-	};
-	use frame_system::{EnsureRoot, EnsureSignedBy};
-	use crate as scheduler;
+impl<T: Config> schedule::v2::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>
+	for Pallet<T>
+{
+	type Address = TaskAddress<T::BlockNumber>;
+	type Hash = T::Hash;
 
-	#[frame_support::pallet]
-	pub mod logger {
-		use super::{OriginCaller, OriginTrait};
-		use frame_support::pallet_prelude::*;
-		use frame_system::pallet_prelude::*;
-		use std::cell::RefCell;
-
-		thread_local! {
-			static LOG: RefCell<Vec<(OriginCaller, u32)>> = RefCell::new(Vec::new());
-		}
-		pub fn log() -> Vec<(OriginCaller, u32)> {
-			LOG.with(|log| log.borrow().clone())
-		}
-
-		#[pallet::pallet]
-		#[pallet::generate_store(pub(super) trait Store)]
-		pub struct Pallet<T>(PhantomData<T>);
-
-		#[pallet::hooks]
-		impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
-
-		#[pallet::config]
-		pub trait Config: frame_system::Config {
-			type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
-		}
-
-		#[pallet::event]
-		#[pallet::generate_deposit(pub(super) fn deposit_event)]
-		pub enum Event<T: Config> {
-			Logged(u32, Weight),
-		}
+	fn schedule(
+		when: DispatchTime<T::BlockNumber>,
+		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+		priority: schedule::Priority,
+		origin: T::PalletsOrigin,
+		call: CallOrHashOf<T>,
+	) -> Result<Self::Address, DispatchError> {
+		Self::do_schedule(when, maybe_periodic, priority, origin, call)
+	}
 
-		#[pallet::call]
-		impl<T: Config> Pallet<T>
-		where
-			<T as frame_system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>,
-		{
-			#[pallet::weight(*weight)]
-			pub fn log(origin: OriginFor<T>, i: u32, weight: Weight) -> DispatchResult {
-				Self::deposit_event(Event::Logged(i, weight));
-				LOG.with(|log| {
-					log.borrow_mut().push((origin.caller().clone(), i));
-				});
-				Ok(())
-			}
+	fn cancel((when, index): Self::Address) -> Result<(), ()> {
+		Self::do_cancel(None, (when, index)).map_err(|_| ())
+	}
 
-			#[pallet::weight(*weight)]
-			pub fn log_without_filter(
-				origin: OriginFor<T>,
-				i: u32,
-				weight: Weight,
-			) -> DispatchResult {
-				Self::deposit_event(Event::Logged(i, weight));
-				LOG.with(|log| {
-					log.borrow_mut().push((origin.caller().clone(), i));
-				});
-				Ok(())
-			}
-		}
+	fn reschedule(
+		address: Self::Address,
+		when: DispatchTime<T::BlockNumber>,
+	) -> Result<Self::Address, DispatchError> {
+		Self::do_reschedule(address, when)
 	}
 
-	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
-	type Block = frame_system::mocking::MockBlock<Test>;
+	fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {
+		Agenda::<T>::get(when)
+			.get(index as usize)
+			.ok_or(())
+			.map(|_| when)
+	}
+}
 
-	frame_support::construct_runtime!(
-		pub enum Test where
-			Block = Block,
-			NodeBlock = Block,
-			UncheckedExtrinsic = UncheckedExtrinsic,
-		{
-			System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
-			Logger: logger::{Pallet, Call, Event<T>},
-			Scheduler: scheduler::{Pallet, Call, Storage, Event<T>},
-		}
-	);
+impl<T: Config> schedule::v2::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>
+	for Pallet<T>
+{
+	type Address = TaskAddress<T::BlockNumber>;
+	type Hash = T::Hash;
 
-	// Scheduler must dispatch with root and no filter, this tests base filter is indeed not used.
-	pub struct BaseFilter;
-	impl Contains<Call> for BaseFilter {
-		fn contains(call: &Call) -> bool {
-			!matches!(call, Call::Logger(logger::Call::log { .. }))
-		}
+	fn schedule_named(
+		id: Vec<u8>,
+		when: DispatchTime<T::BlockNumber>,
+		maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+		priority: schedule::Priority,
+		origin: T::PalletsOrigin,
+		call: CallOrHashOf<T>,
+	) -> Result<Self::Address, ()> {
+		let inner_id: ScheduledId = id
+			.try_into()
+			.unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);
+		Self::do_schedule_named(inner_id, when, maybe_periodic, priority, origin, call)
+			.map_err(|_| ())
 	}
 
-	parameter_types! {
-		pub const BlockHashCount: u64 = 250;
-		pub BlockWeights: frame_system::limits::BlockWeights =
-			frame_system::limits::BlockWeights::simple_max(2_000_000_000_000);
+	fn cancel_named(id: Vec<u8>) -> Result<(), ()> {
+		let inner_id: ScheduledId = id
+			.try_into()
+			.unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);
+		Self::do_cancel_named(None, inner_id).map_err(|_| ())
 	}
-	impl system::Config for Test {
-		type BaseCallFilter = BaseFilter;
-		type BlockWeights = ();
-		type BlockLength = ();
-		type DbWeight = RocksDbWeight;
-		type Origin = Origin;
-		type Call = Call;
-		type Index = u64;
-		type BlockNumber = u64;
-		type Hash = H256;
-		type Hashing = BlakeTwo256;
-		type AccountId = u64;
-		type Lookup = IdentityLookup<Self::AccountId>;
-		type Header = Header;
-		type Event = Event;
-		type BlockHashCount = BlockHashCount;
-		type Version = ();
-		type PalletInfo = PalletInfo;
-		type AccountData = ();
-		type OnNewAccount = ();
-		type OnKilledAccount = ();
-		type SystemWeightInfo = ();
-		type SS58Prefix = ();
-		type OnSetCode = ();
-		type MaxConsumers = ConstU32<16>;
-	}
-	impl logger::Config for Test {
-		type Event = Event;
-	}
-	parameter_types! {
-		pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;
-		pub const MaxScheduledPerBlock: u32 = 10;
-	}
-	ord_parameter_types! {
-		pub const One: u64 = 1;
+
+	fn reschedule_named(
+		id: Vec<u8>,
+		when: DispatchTime<T::BlockNumber>,
+	) -> Result<Self::Address, DispatchError> {
+		let inner_id: ScheduledId = id
+			.try_into()
+			.unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);
+		Self::do_reschedule_named(inner_id, when)
 	}
 
-	impl Config for Test {
-		type Event = Event;
-		type Origin = Origin;
-		type PalletsOrigin = OriginCaller;
-		type Call = Call;
-		type MaximumWeight = MaximumSchedulerWeight;
-		type ScheduleOrigin = EnsureOneOf<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
-		type MaxScheduledPerBlock = MaxScheduledPerBlock;
-		type WeightInfo = ();
-		type SponsorshipHandler = ();
+	fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {
+		let inner_id: ScheduledId = id
+			.try_into()
+			.unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);
+		Lookup::<T>::get(inner_id)
+			.and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))
+			.ok_or(())
 	}
 }
modifiedpallets/scheduler/src/weights.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/weights.rs
+++ b/pallets/scheduler/src/weights.rs
@@ -1,23 +1,6 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-// Original license
 // This file is part of Substrate.
 
-// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// Copyright (C) 2022 Parity Technologies (UK) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,13 +15,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Weights for pallet_scheduler
-//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0
-//! DATE: 2020-10-27, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
-//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
+//! Autogenerated weights for pallet_scheduler
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
 
 // Executed Command:
-// target/release/substrate
+// ./target/production/substrate
 // benchmark
 // --chain=dev
 // --steps=50
@@ -49,78 +33,332 @@
 // --wasm-execution=compiled
 // --heap-pages=4096
 // --output=./frame/scheduler/src/weights.rs
-// --template=./.maintain/frame-weight-template.hbs
+// --template=.maintain/frame-weight-template.hbs
+// --header=HEADER-APACHE2
+// --raw
 
+#![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
 
-use frame_support::{
-	traits::Get,
-	weights::{Weight, constants::RocksDbWeight},
-};
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
 use sp_std::marker::PhantomData;
 
 /// Weight functions needed for pallet_scheduler.
 pub trait WeightInfo {
-	fn schedule(s: u32) -> Weight;
-	fn cancel(s: u32) -> Weight;
-	fn schedule_named(s: u32) -> Weight;
-	fn cancel_named(s: u32) -> Weight;
+	fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight;
+	fn on_initialize_named_resolved(s: u32, ) -> Weight;
+	fn on_initialize_periodic_resolved(s: u32, ) -> Weight;
+	fn on_initialize_resolved(s: u32, ) -> Weight;
+	fn on_initialize_named_aborted(s: u32, ) -> Weight;
+	fn on_initialize_aborted(s: u32, ) -> Weight;
+	fn on_initialize_periodic_named(s: u32, ) -> Weight;
+	fn on_initialize_periodic(s: u32, ) -> Weight;
+	fn on_initialize_named(s: u32, ) -> Weight;
+	fn on_initialize(s: u32, ) -> Weight;
+	fn schedule(s: u32, ) -> Weight;
+	fn cancel(s: u32, ) -> Weight;
+	fn schedule_named(s: u32, ) -> Weight;
+	fn cancel_named(s: u32, ) -> Weight;
 }
 
 /// Weights for pallet_scheduler using the Substrate node and recommended hardware.
 pub struct SubstrateWeight<T>(PhantomData<T>);
 impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
-	fn schedule(s: u32) -> Weight {
-		35_029_000_u64
-			.saturating_add(77_000_u64.saturating_mul(s as Weight))
-			.saturating_add(T::DbWeight::get().reads(1_u64))
-			.saturating_add(T::DbWeight::get().writes(1_u64))
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight {
+		(11_587_000 as Weight)
+			// Standard Error: 17_000
+			.saturating_add((17_428_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named_resolved(s: u32, ) -> Weight {
+		(8_965_000 as Weight)
+			// Standard Error: 11_000
+			.saturating_add((13_410_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(s as Weight)))
 	}
-	fn cancel(s: u32) -> Weight {
-		31_419_000_u64
-			.saturating_add(4_015_000_u64.saturating_mul(s as Weight))
-			.saturating_add(T::DbWeight::get().reads(1_u64))
-			.saturating_add(T::DbWeight::get().writes(2_u64))
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	fn on_initialize_periodic_resolved(s: u32, ) -> Weight {
+		(8_654_000 as Weight)
+			// Standard Error: 17_000
+			.saturating_add((14_990_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	fn on_initialize_resolved(s: u32, ) -> Weight {
+		(9_303_000 as Weight)
+			// Standard Error: 10_000
+			.saturating_add((12_244_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:0)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named_aborted(s: u32, ) -> Weight {
+		(7_506_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((5_208_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:0)
+	fn on_initialize_aborted(s: u32, ) -> Weight {
+		(8_046_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((2_914_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_periodic_named(s: u32, ) -> Weight {
+		(13_704_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((8_186_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	fn on_initialize_periodic(s: u32, ) -> Weight {
+		(12_668_000 as Weight)
+			// Standard Error: 5_000
+			.saturating_add((5_868_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named(s: u32, ) -> Weight {
+		(13_946_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((4_367_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn on_initialize(s: u32, ) -> Weight {
+		(13_151_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((3_455_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn schedule(s: u32, ) -> Weight {
+		(14_040_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((89_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn cancel(s: u32, ) -> Weight {
+		(14_376_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((576_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
-	fn schedule_named(s: u32) -> Weight {
-		44_752_000_u64
-			.saturating_add(123_000_u64.saturating_mul(s as Weight))
-			.saturating_add(T::DbWeight::get().reads(2_u64))
-			.saturating_add(T::DbWeight::get().writes(2_u64))
+	// Storage: Scheduler Lookup (r:1 w:1)
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn schedule_named(s: u32, ) -> Weight {
+		(16_806_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((102_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
-	fn cancel_named(s: u32) -> Weight {
-		35_712_000_u64
-			.saturating_add(4_008_000_u64.saturating_mul(s as Weight))
-			.saturating_add(T::DbWeight::get().reads(2_u64))
-			.saturating_add(T::DbWeight::get().writes(2_u64))
+	// Storage: Scheduler Lookup (r:1 w:1)
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn cancel_named(s: u32, ) -> Weight {
+		(15_852_000 as Weight)
+			// Standard Error: 2_000
+			.saturating_add((590_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 }
 
 // For backwards compatibility and tests
 impl WeightInfo for () {
-	fn schedule(s: u32) -> Weight {
-		35_029_000_u64
-			.saturating_add(77_000_u64.saturating_mul(s as Weight))
-			.saturating_add(RocksDbWeight::get().reads(1_u64))
-			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight {
+		(11_587_000 as Weight)
+			// Standard Error: 17_000
+			.saturating_add((17_428_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named_resolved(s: u32, ) -> Weight {
+		(8_965_000 as Weight)
+			// Standard Error: 11_000
+			.saturating_add((13_410_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(s as Weight)))
 	}
-	fn cancel(s: u32) -> Weight {
-		31_419_000_u64
-			.saturating_add(4_015_000_u64.saturating_mul(s as Weight))
-			.saturating_add(RocksDbWeight::get().reads(1_u64))
-			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	fn on_initialize_periodic_resolved(s: u32, ) -> Weight {
+		(8_654_000 as Weight)
+			// Standard Error: 17_000
+			.saturating_add((14_990_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(s as Weight)))
 	}
-	fn schedule_named(s: u32) -> Weight {
-		44_752_000_u64
-			.saturating_add(123_000_u64.saturating_mul(s as Weight))
-			.saturating_add(RocksDbWeight::get().reads(2_u64))
-			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Preimage PreimageFor (r:1 w:1)
+	// Storage: Preimage StatusFor (r:1 w:1)
+	fn on_initialize_resolved(s: u32, ) -> Weight {
+		(9_303_000 as Weight)
+			// Standard Error: 10_000
+			.saturating_add((12_244_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(s as Weight)))
 	}
-	fn cancel_named(s: u32) -> Weight {
-		35_712_000_u64
-			.saturating_add(4_008_000_u64.saturating_mul(s as Weight))
-			.saturating_add(RocksDbWeight::get().reads(2_u64))
-			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:0)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named_aborted(s: u32, ) -> Weight {
+		(7_506_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((5_208_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Preimage PreimageFor (r:1 w:0)
+	fn on_initialize_aborted(s: u32, ) -> Weight {
+		(8_046_000 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((2_914_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_periodic_named(s: u32, ) -> Weight {
+		(13_704_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((8_186_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:2 w:2)
+	fn on_initialize_periodic(s: u32, ) -> Weight {
+		(12_668_000 as Weight)
+			// Standard Error: 5_000
+			.saturating_add((5_868_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn on_initialize_named(s: u32, ) -> Weight {
+		(13_946_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((4_367_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight)))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn on_initialize(s: u32, ) -> Weight {
+		(13_151_000 as Weight)
+			// Standard Error: 4_000
+			.saturating_add((3_455_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn schedule(s: u32, ) -> Weight {
+		(14_040_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((89_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Scheduler Agenda (r:1 w:1)
+	// Storage: Scheduler Lookup (r:0 w:1)
+	fn cancel(s: u32, ) -> Weight {
+		(14_376_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((576_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Scheduler Lookup (r:1 w:1)
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn schedule_named(s: u32, ) -> Weight {
+		(16_806_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((102_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+	}
+	// Storage: Scheduler Lookup (r:1 w:1)
+	// Storage: Scheduler Agenda (r:1 w:1)
+	fn cancel_named(s: u32, ) -> Weight {
+		(15_852_000 as Weight)
+			// Standard Error: 2_000
+			.saturating_add((590_000 as Weight).saturating_mul(s as Weight))
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 }
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -28,6 +28,8 @@
 use sp_api::impl_runtime_apis;
 use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
 use sp_runtime::DispatchError;
+use fp_self_contained::*;
+use sp_runtime::traits::{Member};
 // #[cfg(any(feature = "std", test))]
 // pub use sp_runtime::BuildStorage;
 
@@ -59,7 +61,7 @@
 	traits::{
 		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
 		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
-		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance,
+		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
 	},
 	weights::{
 		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
@@ -67,8 +69,13 @@
 		WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
 	},
 };
-use up_data_structs::mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping};
-use up_data_structs::*;
+use pallet_unq_scheduler::DispatchCall;
+use up_data_structs::{
+	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
+	CollectionStats, RpcCollection,
+	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
+};
+
 // use pallet_contracts::weights::WeightInfo;
 // #[cfg(any(feature = "std", test))]
 use frame_system::{
@@ -79,12 +86,17 @@
 	traits::{BaseArithmetic, Unsigned},
 };
 use smallvec::smallvec;
+// use scale_info::TypeInfo;
 use codec::{Encode, Decode};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
-	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf, Saturating},
+	traits::{
+		Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf,
+		Saturating, CheckedConversion,
+	},
+	generic::Era,
 	transaction_validity::TransactionValidityError,
-	SaturatedConversion,
+	DispatchErrorWithPostInfo, SaturatedConversion,
 };
 
 // pub use pallet_timestamp::Call as TimestampCall;
@@ -102,7 +114,7 @@
 	ParentIsPreset,
 };
 use xcm_executor::{Config, XcmExecutor, Assets};
-use sp_std::{marker::PhantomData};
+use sp_std::{cmp::Ordering, marker::PhantomData};
 
 use xcm::latest::{
 	//	Xcm,
@@ -113,7 +125,6 @@
 };
 use xcm_executor::traits::{MatchesFungible, WeightTrader};
 //use xcm_executor::traits::MatchesFungible;
-use sp_runtime::traits::CheckedConversion;
 
 use unique_runtime_common::{
 	impl_common_runtime_apis,
@@ -406,12 +417,13 @@
 	// pub const ExistentialDeposit: u128 = 500;
 	pub const ExistentialDeposit: u128 = 0;
 	pub const MaxLocks: u32 = 50;
+	pub const MaxReserves: u32 = 50;
 }
 
 impl pallet_balances::Config for Runtime {
 	type MaxLocks = MaxLocks;
-	type MaxReserves = ();
-	type ReserveIdentifier = [u8; 8];
+	type MaxReserves = MaxReserves;
+	type ReserveIdentifier = [u8; 16];
 	/// The type for recording an account's balance.
 	type Balance = Balance;
 	/// The ubiquitous event type.
@@ -930,33 +942,156 @@
 	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
 }
 
-// parameter_types! {
-// 	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-// 		RuntimeBlockWeights::get().max_block;
-// 	pub const MaxScheduledPerBlock: u32 = 50;
-// }
+parameter_types! {
+	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
+		RuntimeBlockWeights::get().max_block;
+	pub const MaxScheduledPerBlock: u32 = 50;
+}
+
+type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
+use frame_support::traits::NamedReservableCurrency;
+
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
+	(
+		frame_system::CheckSpecVersion::<Runtime>::new(),
+		frame_system::CheckGenesis::<Runtime>::new(),
+		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
+		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
+			from,
+		)),
+		frame_system::CheckWeight::<Runtime>::new(),
+		// sponsoring transaction logic
+		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+	)
+}
+
+pub struct SchedulerPaymentExecutor;
+impl<T: frame_system::Config + pallet_unq_scheduler::Config, SelfContainedSignedInfo>
+	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
+where
+	<T as frame_system::Config>::Call: Member
+		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
+		+ GetDispatchInfo
+		+ From<frame_system::Call<Runtime>>,
+	SelfContainedSignedInfo: Send + Sync + 'static,
+	Call: From<<T as frame_system::Config>::Call>
+		+ From<<T as pallet_unq_scheduler::Config>::Call>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
+	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
+{
+	fn dispatch_call(
+		signer: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+	) -> Result<
+		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
+		TransactionValidityError,
+	> {
+		let dispatch_info = call.get_dispatch_info();
+		let extrinsic = fp_self_contained::CheckedExtrinsic::<
+			AccountId,
+			Call,
+			SignedExtraScheduler,
+			SelfContainedSignedInfo,
+		> {
+			signed:
+				CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
+					signer.clone().into(),
+					get_signed_extras(signer.into()),
+				),
+			function: call.into(),
+		};
+
+		extrinsic.apply::<Runtime>(&dispatch_info, 0)
+	}
+
+	fn reserve_balance(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+		count: u32,
+	) -> Result<(), DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+			.saturating_mul(count.into());
+
+		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
+			&id,
+			&(sponsor.into()),
+			weight,
+		)
+	}
+
+	fn pay_for_call(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+	) -> Result<u128, DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				weight,
+			),
+		)
+	}
+
+	fn cancel_reserve(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+	) -> Result<u128, DispatchError> {
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				u128::MAX,
+			),
+		)
+	}
+}
+
+parameter_types! {
+	pub const NoPreimagePostponement: Option<u32> = Some(10);
+	pub const Preimage: Option<u32> = Some(10);
+}
+
+/// Used the compare the privilege of an origin inside the scheduler.
+pub struct OriginPrivilegeCmp;
+
+impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
+	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
+		Some(Ordering::Equal)
+	}
+}
+
+impl pallet_unq_scheduler::Config for Runtime {
+	type Event = Event;
+	type Origin = Origin;
+	type Currency = Balances;
+	type PalletsOrigin = OriginCaller;
+	type Call = Call;
+	type MaximumWeight = MaximumSchedulerWeight;
+	type ScheduleOrigin = EnsureSigned<AccountId>;
+	type MaxScheduledPerBlock = MaxScheduledPerBlock;
+	type WeightInfo = ();
+	type CallExecutor = SchedulerPaymentExecutor;
+	type OriginPrivilegeCmp = OriginPrivilegeCmp;
+	type PreimageProvider = ();
+	type NoPreimagePostponement = NoPreimagePostponement;
+}
 
 type EvmSponsorshipHandler = (
 	UniqueEthSponsorshipHandler<Runtime>,
 	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
 );
+
 type SponsorshipHandler = (
 	UniqueSponsorshipHandler<Runtime>,
 	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
 	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
 );
-
-// impl pallet_unq_scheduler::Config for Runtime {
-// 	type Event = Event;
-// 	type Origin = Origin;
-// 	type PalletsOrigin = OriginCaller;
-// 	type Call = Call;
-// 	type MaximumWeight = MaximumSchedulerWeight;
-// 	type ScheduleOrigin = EnsureSigned<AccountId>;
-// 	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-// 	type SponsorshipHandler = SponsorshipHandler;
-// 	type WeightInfo = ();
-// }
 
 impl pallet_evm_transaction_payment::Config for Runtime {
 	type EvmSponsorshipHandler = EvmSponsorshipHandler;
@@ -1020,7 +1155,7 @@
 		// Unique Pallets
 		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
 		Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
-		// Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
+		Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
 		// free = 63
 		Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
 		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
@@ -1087,10 +1222,17 @@
 	frame_system::CheckEra<Runtime>,
 	frame_system::CheckNonce<Runtime>,
 	frame_system::CheckWeight<Runtime>,
-	pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
+	ChargeTransactionPayment,
 	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
 	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
 );
+pub type SignedExtraScheduler = (
+	frame_system::CheckSpecVersion<Runtime>,
+	frame_system::CheckGenesis<Runtime>,
+	frame_system::CheckEra<Runtime>,
+	frame_system::CheckNonce<Runtime>,
+	frame_system::CheckWeight<Runtime>,
+);
 /// Unchecked extrinsic type as expected by this runtime.
 pub type UncheckedExtrinsic =
 	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -33,7 +33,7 @@
 
 use sp_runtime::{
 	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
-	traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero},
+	traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member},
 	transaction_validity::{TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, RuntimeAppPublic,
 };
@@ -58,7 +58,7 @@
 	traits::{
 		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
 		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
-		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance,
+		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
 	},
 	weights::{
 		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
@@ -81,18 +81,28 @@
 use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
-	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
+	traits::{
+		Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating,
+		CheckedConversion,
+	},
+	generic::Era,
 	transaction_validity::TransactionValidityError,
-	SaturatedConversion,
+	SaturatedConversion, DispatchErrorWithPostInfo,
 };
 
+use fp_self_contained::{SelfContainedCall, CheckedSignature};
+
 // pub use pallet_timestamp::Call as TimestampCall;
 pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
 
 // Polkadot imports
 use pallet_xcm::XcmPassthrough;
 use polkadot_parachain::primitives::Sibling;
-use up_data_structs::mapping::{CrossTokenAddressMapping, EvmTokenAddressMapping};
+use up_data_structs::{
+	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits, 
+	CollectionStats, RpcCollection, 
+	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}
+};
 use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
 use xcm_builder::{
 	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
@@ -102,7 +112,8 @@
 	ParentIsPreset,
 };
 use xcm_executor::{Config, XcmExecutor, Assets};
-use sp_std::{marker::PhantomData};
+use sp_std::{cmp::Ordering, marker::PhantomData};
+use pallet_unq_scheduler::DispatchCall;
 
 use xcm::latest::{
 	//	Xcm,
@@ -112,8 +123,6 @@
 	Error as XcmError,
 };
 use xcm_executor::traits::{MatchesFungible, WeightTrader};
-//use xcm_executor::traits::MatchesFungible;
-use sp_runtime::traits::CheckedConversion;
 
 use unique_runtime_common::{
 	impl_common_runtime_apis,
@@ -390,7 +399,7 @@
 impl pallet_balances::Config for Runtime {
 	type MaxLocks = MaxLocks;
 	type MaxReserves = ();
-	type ReserveIdentifier = [u8; 8];
+	type ReserveIdentifier = [u8; 16];
 	/// The type for recording an account's balance.
 	type Balance = Balance;
 	/// The ubiquitous event type.
@@ -913,11 +922,11 @@
 	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
 }
 
-// parameter_types! {
-// 	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-// 		RuntimeBlockWeights::get().max_block;
-// 	pub const MaxScheduledPerBlock: u32 = 50;
-// }
+parameter_types! {
+	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
+		RuntimeBlockWeights::get().max_block;
+	pub const MaxScheduledPerBlock: u32 = 50;
+}
 
 type EvmSponsorshipHandler = (
 	UniqueEthSponsorshipHandler<Runtime>,
@@ -929,17 +938,34 @@
 	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
 );
 
-// impl pallet_unq_scheduler::Config for Runtime {
-// 	type Event = Event;
-// 	type Origin = Origin;
-// 	type PalletsOrigin = OriginCaller;
-// 	type Call = Call;
-// 	type MaximumWeight = MaximumSchedulerWeight;
-// 	type ScheduleOrigin = EnsureSigned<AccountId>;
-// 	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-// 	type SponsorshipHandler = SponsorshipHandler;
-// 	type WeightInfo = ();
-// }
+parameter_types! {
+	pub const NoPreimagePostponement: Option<u32> = Some(10);
+	pub const Preimage: Option<u32> = Some(10);
+}
+
+/// Used the compare the privilege of an origin inside the scheduler.
+pub struct OriginPrivilegeCmp;
+impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
+	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
+		Some(Ordering::Equal)
+	}
+}
+
+impl pallet_unq_scheduler::Config for Runtime {
+	type Event = Event;
+	type Origin = Origin;
+	type Currency = Balances;
+	type PalletsOrigin = OriginCaller;
+	type Call = Call;
+	type MaximumWeight = MaximumSchedulerWeight;
+	type ScheduleOrigin = EnsureSigned<AccountId>;
+	type MaxScheduledPerBlock = MaxScheduledPerBlock;
+	type WeightInfo = ();
+	type CallExecutor = SchedulerPaymentExecutor;
+	type OriginPrivilegeCmp = OriginPrivilegeCmp;
+	type PreimageProvider = ();
+	type NoPreimagePostponement = NoPreimagePostponement;
+}
 
 impl pallet_evm_transaction_payment::Config for Runtime {
 	type EvmSponsorshipHandler = EvmSponsorshipHandler;
@@ -954,6 +980,110 @@
 //	 type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
 // }
 
+type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
+use frame_support::traits::NamedReservableCurrency;
+
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
+	(
+		frame_system::CheckSpecVersion::<Runtime>::new(),
+		frame_system::CheckGenesis::<Runtime>::new(),
+		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
+		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
+			from,
+		)),
+		frame_system::CheckWeight::<Runtime>::new(),
+		// sponsoring transaction logic
+		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+	)
+}
+
+pub struct SchedulerPaymentExecutor;
+impl<T: frame_system::Config + pallet_unq_scheduler::Config, SelfContainedSignedInfo>
+	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
+where
+	<T as frame_system::Config>::Call: Member
+		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
+		+ GetDispatchInfo
+		+ From<frame_system::Call<Runtime>>,
+	SelfContainedSignedInfo: Send + Sync + 'static,
+	Call: From<<T as frame_system::Config>::Call>
+		+ From<<T as pallet_unq_scheduler::Config>::Call>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
+	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
+{
+	fn dispatch_call(
+		signer: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+	) -> Result<
+		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
+		TransactionValidityError,
+	> {
+		let dispatch_info = call.get_dispatch_info();
+		let extrinsic = fp_self_contained::CheckedExtrinsic::<
+			AccountId,
+			Call,
+			SignedExtraScheduler,
+			SelfContainedSignedInfo,
+		> {
+			signed:
+				CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
+					signer.clone().into(),
+					get_signed_extras(signer.into()),
+				),
+			function: call.into(),
+		};
+
+		extrinsic.apply::<Runtime>(&dispatch_info, 0)
+	}
+
+	fn reserve_balance(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+		count: u32,
+	) -> Result<(), DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+			.saturating_mul(count.into());
+
+		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
+			&id,
+			&(sponsor.into()),
+			weight.into(),
+		)
+	}
+
+	fn pay_for_call(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unq_scheduler::Config>::Call,
+	) -> Result<u128, DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				weight.into(),
+			),
+		)
+	}
+
+	fn cancel_reserve(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+	) -> Result<u128, DispatchError> {
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				u128::MAX,
+			),
+		)
+	}
+}
+
 parameter_types! {
 	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049
 	pub const HelpersContractAddress: H160 = H160([
@@ -1003,7 +1133,7 @@
 		// Unique Pallets
 		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
 		Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
-		// Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
+		Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
 		// free = 63
 		Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
 		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
@@ -1073,6 +1203,15 @@
 	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
 	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
 );
+
+pub type SignedExtraScheduler = (
+	frame_system::CheckSpecVersion<Runtime>,
+	frame_system::CheckGenesis<Runtime>,
+	frame_system::CheckEra<Runtime>,
+	frame_system::CheckNonce<Runtime>,
+	frame_system::CheckWeight<Runtime>,
+	// pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
+);
 /// Unchecked extrinsic type as expected by this runtime.
 pub type UncheckedExtrinsic =
 	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
3333
34use sp_runtime::{34use sp_runtime::{
35 Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,35 Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
36 generic::Era,
36 traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero},37 traits::{
38 Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating,
39 CheckedConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion,
40 Zero, Member,
41 },
37 transaction_validity::{TransactionSource, TransactionValidity},42 transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError},
38 ApplyExtrinsicResult, RuntimeAppPublic,43 ApplyExtrinsicResult, RuntimeAppPublic, SaturatedConversion, DispatchErrorWithPostInfo,
39};44};
45
46use fp_self_contained::{SelfContainedCall, CheckedSignature};
47use sp_std::{cmp::Ordering, marker::PhantomData};
48use pallet_unq_scheduler::DispatchCall;
4049
41use sp_std::prelude::*;50use sp_std::prelude::*;
4251
59 traits::{68 traits::{
60 tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,69 tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
61 Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,70 Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
62 OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance,71 OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
63 },72 },
64 weights::{73 weights::{
65 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},74 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
86use smallvec::smallvec;95use smallvec::smallvec;
87use codec::{Encode, Decode};96use codec::{Encode, Decode};
88use fp_rpc::TransactionStatus;97use fp_rpc::TransactionStatus;
89use sp_runtime::{
90 traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
91 transaction_validity::TransactionValidityError,
92 SaturatedConversion,
93};
9498
95// pub use pallet_timestamp::Call as TimestampCall;99// pub use pallet_timestamp::Call as TimestampCall;
96100
97// Polkadot imports101// Polkadot imports
98use pallet_xcm::XcmPassthrough;102use pallet_xcm::XcmPassthrough;
99use polkadot_parachain::primitives::Sibling;103use polkadot_parachain::primitives::Sibling;
100use up_data_structs::mapping::{CrossTokenAddressMapping, EvmTokenAddressMapping};104use up_data_structs::{
105 CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
106 CollectionStats, RpcCollection,
107 mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}
108};
101use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};109use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
102use xcm_builder::{110use xcm_builder::{
103 AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,111 AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
107 ParentIsPreset,115 ParentIsPreset,
108};116};
109use xcm_executor::{Config, XcmExecutor, Assets};117use xcm_executor::{Config, XcmExecutor, Assets};
110use sp_std::{marker::PhantomData};
111118
112use xcm::latest::{119use xcm::latest::{
113 // Xcm,120 // Xcm,
117 Error as XcmError,124 Error as XcmError,
118};125};
119use xcm_executor::traits::{MatchesFungible, WeightTrader};126use xcm_executor::traits::{MatchesFungible, WeightTrader};
120//use xcm_executor::traits::MatchesFungible;
121use sp_runtime::traits::CheckedConversion;127use sp_runtime::traits::CheckedConversion;
122128
123use unique_runtime_common::{129use unique_runtime_common::{
395impl pallet_balances::Config for Runtime {401impl pallet_balances::Config for Runtime {
396 type MaxLocks = MaxLocks;402 type MaxLocks = MaxLocks;
397 type MaxReserves = ();403 type MaxReserves = ();
398 type ReserveIdentifier = [u8; 8];404 type ReserveIdentifier = [u8; 16];
399 /// The type for recording an account's balance.405 /// The type for recording an account's balance.
400 type Balance = Balance;406 type Balance = Balance;
401 /// The ubiquitous event type.407 /// The ubiquitous event type.
918 type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;924 type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
919}925}
920926
921// parameter_types! {927parameter_types! {
922// pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *928 pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
923// RuntimeBlockWeights::get().max_block;929 RuntimeBlockWeights::get().max_block;
924// pub const MaxScheduledPerBlock: u32 = 50;930 pub const MaxScheduledPerBlock: u32 = 50;
925// }931}
932
933parameter_types! {
934 pub const NoPreimagePostponement: Option<u32> = Some(10);
935 pub const Preimage: Option<u32> = Some(10);
936}
937
938/// Used the compare the privilege of an origin inside the scheduler.
939pub struct OriginPrivilegeCmp;
940impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
941 fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
942 Some(Ordering::Equal)
943 }
944}
945
946impl pallet_unq_scheduler::Config for Runtime {
947 type Event = Event;
948 type Origin = Origin;
949 type Currency = Balances;
950 type PalletsOrigin = OriginCaller;
951 type Call = Call;
952 type MaximumWeight = MaximumSchedulerWeight;
953 type ScheduleOrigin = EnsureSigned<AccountId>;
954 type MaxScheduledPerBlock = MaxScheduledPerBlock;
955 type WeightInfo = ();
956 type CallExecutor = SchedulerPaymentExecutor;
957 type OriginPrivilegeCmp = OriginPrivilegeCmp;
958 type PreimageProvider = ();
959 type NoPreimagePostponement = NoPreimagePostponement;
960}
961
962type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
963use frame_support::traits::NamedReservableCurrency;
964
965fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
966 (
967 frame_system::CheckSpecVersion::<Runtime>::new(),
968 frame_system::CheckGenesis::<Runtime>::new(),
969 frame_system::CheckEra::<Runtime>::from(Era::Immortal),
970 frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
971 from,
972 )),
973 frame_system::CheckWeight::<Runtime>::new(),
974 // sponsoring transaction logic
975 // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
976 )
977}
978
979pub struct SchedulerPaymentExecutor;
980impl<T: frame_system::Config + pallet_unq_scheduler::Config, SelfContainedSignedInfo>
981 DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
982where
983 <T as frame_system::Config>::Call: Member
984 + Dispatchable<Origin = Origin, Info = DispatchInfo>
985 + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
986 + GetDispatchInfo
987 + From<frame_system::Call<Runtime>>,
988 SelfContainedSignedInfo: Send + Sync + 'static,
989 Call: From<<T as frame_system::Config>::Call>
990 + From<<T as pallet_unq_scheduler::Config>::Call>
991 + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
992 sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
993{
994 fn dispatch_call(
995 signer: <T as frame_system::Config>::AccountId,
996 call: <T as pallet_unq_scheduler::Config>::Call,
997 ) -> Result<
998 Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
999 TransactionValidityError,
1000 > {
1001 let dispatch_info = call.get_dispatch_info();
1002 let extrinsic = fp_self_contained::CheckedExtrinsic::<
1003 AccountId,
1004 Call,
1005 SignedExtraScheduler,
1006 SelfContainedSignedInfo,
1007 > {
1008 signed:
1009 CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
1010 signer.clone().into(),
1011 get_signed_extras(signer.into()),
1012 ),
1013 function: call.into(),
1014 };
1015
1016 extrinsic.apply::<Runtime>(&dispatch_info, 0)
1017 }
1018
1019 fn reserve_balance(
1020 id: [u8; 16],
1021 sponsor: <T as frame_system::Config>::AccountId,
1022 call: <T as pallet_unq_scheduler::Config>::Call,
1023 count: u32,
1024 ) -> Result<(), DispatchError> {
1025 let dispatch_info = call.get_dispatch_info();
1026 let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
1027 .saturating_mul(count.into());
1028
1029 <Balances as NamedReservableCurrency<AccountId>>::reserve_named(
1030 &id,
1031 &(sponsor.into()),
1032 weight.into(),
1033 )
1034 }
1035
1036 fn pay_for_call(
1037 id: [u8; 16],
1038 sponsor: <T as frame_system::Config>::AccountId,
1039 call: <T as pallet_unq_scheduler::Config>::Call,
1040 ) -> Result<u128, DispatchError> {
1041 let dispatch_info = call.get_dispatch_info();
1042 let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
1043 Ok(
1044 <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
1045 &id,
1046 &(sponsor.into()),
1047 weight.into(),
1048 ),
1049 )
1050 }
1051
1052 fn cancel_reserve(
1053 id: [u8; 16],
1054 sponsor: <T as frame_system::Config>::AccountId,
1055 ) -> Result<u128, DispatchError> {
1056 Ok(
1057 <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
1058 &id,
1059 &(sponsor.into()),
1060 u128::MAX,
1061 ),
1062 )
1063 }
1064}
9261065
927type EvmSponsorshipHandler = (1066type EvmSponsorshipHandler = (
928 UniqueEthSponsorshipHandler<Runtime>,1067 UniqueEthSponsorshipHandler<Runtime>,
934 pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,1073 pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
935);1074);
936
937// impl pallet_unq_scheduler::Config for Runtime {
938// type Event = Event;
939// type Origin = Origin;
940// type PalletsOrigin = OriginCaller;
941// type Call = Call;
942// type MaximumWeight = MaximumSchedulerWeight;
943// type ScheduleOrigin = EnsureSigned<AccountId>;
944// type MaxScheduledPerBlock = MaxScheduledPerBlock;
945// type SponsorshipHandler = SponsorshipHandler;
946// type WeightInfo = ();
947// }
9481075
949impl pallet_evm_transaction_payment::Config for Runtime {1076impl pallet_evm_transaction_payment::Config for Runtime {
950 type EvmSponsorshipHandler = EvmSponsorshipHandler;1077 type EvmSponsorshipHandler = EvmSponsorshipHandler;
1008 // Unique Pallets1135 // Unique Pallets
1009 Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,1136 Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
1010 Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,1137 Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
1011 // Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,1138 Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
1012 // free = 631139 // free = 63
1013 Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,1140 Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
1014 // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,1141 // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
1078 //pallet_contract_helpers::ContractHelpersExtension<Runtime>,1205 //pallet_contract_helpers::ContractHelpersExtension<Runtime>,
1079 pallet_ethereum::FakeTransactionFinalizer<Runtime>,1206 pallet_ethereum::FakeTransactionFinalizer<Runtime>,
1080);1207);
1208pub type SignedExtraScheduler = (
1209 frame_system::CheckSpecVersion<Runtime>,
1210 frame_system::CheckGenesis<Runtime>,
1211 frame_system::CheckEra<Runtime>,
1212 frame_system::CheckNonce<Runtime>,
1213 frame_system::CheckWeight<Runtime>,
1214 // pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
1215);
1081/// Unchecked extrinsic type as expected by this runtime.1216/// Unchecked extrinsic type as expected by this runtime.
1082pub type UncheckedExtrinsic =1217pub type UncheckedExtrinsic =
1083 fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;1218 fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -68,6 +68,8 @@
     "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
     "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
     "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
+    "testScheduler": "mocha --timeout 9999999 -r ts-node/register ./**/scheduler.test.ts",
+    "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts",
     "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",
     "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
addedtests/src/eth/scheduling.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/scheduling.test.ts
@@ -0,0 +1,55 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect} from 'chai';
+import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers';
+import {scheduleExpectSuccess, waitNewBlocks} from '../util/helpers';
+import privateKey from '../substrate/privateKey';
+
+describe('Scheduing EVM smart contracts', () => {
+  itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3}) => {
+    const deployer = await createEthAccountWithBalance(api, web3);
+    const flipper = await deployFlipper(web3, deployer);
+    const initialValue = await flipper.methods.getValue().call();
+    const alice = privateKey('//Alice');
+    await transferBalanceToEth(api, alice, subToEth(alice.address));
+
+    {
+      const tx = api.tx.evm.call(
+        subToEth(alice.address),
+        flipper.options.address,
+        flipper.methods.flip().encodeABI(),
+        '0',
+        GAS_ARGS.gas,
+        await web3.eth.getGasPrice(),
+        null,
+        null,
+        [],
+      );
+      const waitForBlocks = 4;
+      const periodBlocks = 2;
+
+      await scheduleExpectSuccess(tx, alice, waitForBlocks, '0x' + '0'.repeat(32), periodBlocks, 2);
+      expect(await flipper.methods.getValue().call()).to.be.equal(initialValue);
+      
+      await waitNewBlocks(waitForBlocks - 1);
+      expect(await flipper.methods.getValue().call()).to.be.not.equal(initialValue);
+  
+      await waitNewBlocks(periodBlocks);
+      expect(await flipper.methods.getValue().call()).to.be.equal(initialValue);
+    }
+  });
+});
\ No newline at end of file
modifiedtests/src/pallet-presence.test.tsdiffbeforeafterboth
--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -50,7 +50,7 @@
   'unique',
   'nonfungible',
   'refungible',
-  //'scheduler',
+  'scheduler',
   'charging',
 ];
 
modifiedtests/src/scheduler.test.tsdiffbeforeafterboth
--- a/tests/src/scheduler.test.ts
+++ b/tests/src/scheduler.test.ts
@@ -14,32 +14,197 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import chai from 'chai';
+import chai, {expect} from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
-import usingApi from './substrate/substrate-api';
 import {
+  default as usingApi, 
+  submitTransactionAsync,
+} from './substrate/substrate-api';
+import {
   createItemExpectSuccess,
   createCollectionExpectSuccess,
   scheduleTransferExpectSuccess,
+  scheduleTransferAndWaitExpectSuccess,
   setCollectionSponsorExpectSuccess,
   confirmSponsorshipExpectSuccess,
+  findUnusedAddress,
+  UNIQUE,
+  enablePublicMintingExpectSuccess,
+  addToAllowListExpectSuccess,
+  waitNewBlocks,
+  normalizeAccountId,
+  getTokenOwner,
+  getGenericResult,
+  scheduleTransferFundsPeriodicExpectSuccess,
+  getFreeBalance,
+  confirmSponsorshipByKeyExpectSuccess,
+  scheduleExpectFailure,
 } from './util/helpers';
+import {IKeyringPair} from '@polkadot/types/types';
 
 chai.use(chaiAsPromised);
 
-describe.skip('Integration Test scheduler base transaction', () => {
-  it('User can transfer owned token with delay (scheduler)', async () => {
+describe.skip('Scheduling token and balance transfers', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let scheduledIdBase: string;
+  let scheduledIdSlider: number;
+
+  before(async() => {
     await usingApi(async () => {
-      const alice = privateKey('//Alice');
-      const bob = privateKey('//Bob');
-      // nft
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+    });
+
+    scheduledIdBase = '0x' + '0'.repeat(31);
+    scheduledIdSlider = 0;
+  });
+
+  // Loop scheduledId around 10. Unless there are concurrent tasks with long periods/repetitions, tests' tasks' ids shouldn't ovelap.
+  function makeScheduledId(): string {
+    return scheduledIdBase + ((scheduledIdSlider++) % 10);
+  }
+
+  it('Can schedule a transfer of an owned token with delay', async () => {
+    await usingApi(async () => {
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
       await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);
       await confirmSponsorshipExpectSuccess(nftCollectionId);
 
-      await scheduleTransferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4);
+      await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4, makeScheduledId());
+    });
+  });
+
+  it('Can transfer funds periodically', async () => {
+    await usingApi(async () => {
+      const waitForBlocks = 4;
+      const period = 2;
+      await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, makeScheduledId(), period, 2);
+      const bobsBalanceBefore = await getFreeBalance(bob);
+
+      // discounting already waited-for operations
+      await waitNewBlocks(waitForBlocks - 2);
+      const bobsBalanceAfterFirst = await getFreeBalance(bob);
+      expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;
+
+      await waitNewBlocks(period);
+      const bobsBalanceAfterSecond = await getFreeBalance(bob);
+      expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;
+    });
+  });
+
+  it('Can sponsor scheduling a transaction', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async () => {
+      const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+      const bobBalanceBefore = await getFreeBalance(bob);
+      const waitForBlocks = 4;
+      // no need to wait to check, fees must be deducted on scheduling, immediately
+      await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, waitForBlocks, makeScheduledId());
+      const bobBalanceAfter = await getFreeBalance(bob);
+      // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;
+      expect(bobBalanceAfter < bobBalanceBefore).to.be.true;
+      // wait for sequentiality matters
+      await waitNewBlocks(waitForBlocks - 1);
+    });
+  });
+
+  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {
+    await usingApi(async (api) => {
+      // Find an empty, unused account
+      const zeroBalance = await findUnusedAddress(api);
+
+      const collectionId = await createCollectionExpectSuccess();
+
+      // Add zeroBalance address to allow list
+      await enablePublicMintingExpectSuccess(alice, collectionId);
+      await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+      // Grace zeroBalance with money, enough to cover future transactions
+      const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+      await submitTransactionAsync(alice, balanceTx);
+
+      // Mint a fresh NFT
+      const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');
+
+      // Schedule transfer of the NFT a few blocks ahead
+      const waitForBlocks = 5;
+      await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId());
+
+      // Get rid of the account's funds before the scheduled transaction takes place
+      const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);
+      const events = await submitTransactionAsync(zeroBalance, balanceTx2);
+      expect(getGenericResult(events).success).to.be.true;
+      /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?
+      const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);
+      const events = await submitTransactionAsync(alice, sudoTx);
+      expect(getGenericResult(events).success).to.be.true;*/
+
+      // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions
+      await waitNewBlocks(waitForBlocks - 3);
+
+      expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));
+    });
+  });
+
+  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+
+    await usingApi(async (api) => {
+      const zeroBalance = await findUnusedAddress(api);
+      const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+      await submitTransactionAsync(alice, balanceTx);
+
+      await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);
+      await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);
+
+      const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+      const waitForBlocks = 5;
+      await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, makeScheduledId());
+
+      const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);
+      const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);
+      const events = await submitTransactionAsync(alice, sudoTx);
+      expect(getGenericResult(events).success).to.be.true;
+
+      // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions
+      await waitNewBlocks(waitForBlocks - 3);
+
+      expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));
+    });
+  });
+
+  it.skip('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const zeroBalance = await findUnusedAddress(api);
+
+      await enablePublicMintingExpectSuccess(alice, collectionId);
+      await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+      const bobBalanceBefore = await getFreeBalance(bob);
+
+      const createData = {nft: {const_data: [], variable_data: []}};
+      const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);
+
+      /*const badTransaction = async function () {
+        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+      };
+      await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/
+
+      await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3);
+
+      expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);
     });
   });
 });
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -630,10 +630,16 @@
 }
 
 export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed = '//Alice') {
+  await usingApi(async () => {
+    const sender = privateKey(senderSeed);
+    await confirmSponsorshipByKeyExpectSuccess(collectionId, sender);
+  });
+}
+
+export async function confirmSponsorshipByKeyExpectSuccess(collectionId: number, sender: IKeyringPair) {
   await usingApi(async (api) => {
 
     // Run the transaction
-    const sender = privateKey(senderSeed);
     const tx = api.tx.unique.confirmSponsorship(collectionId);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
@@ -899,7 +905,7 @@
 }
 
 /* eslint no-async-promise-executor: "off" */
-async function getBlockNumber(api: ApiPromise): Promise<number> {
+export async function getBlockNumber(api: ApiPromise): Promise<number> {
   return new Promise<number>(async (resolve) => {
     const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => {
       unsubscribe();
@@ -935,29 +941,76 @@
 }
 
 export async function
-scheduleTransferExpectSuccess(
-  collectionId: number,
-  tokenId: number,
+scheduleExpectSuccess(
+  operationTx: any,
   sender: IKeyringPair,
-  recipient: IKeyringPair,
-  value: number | bigint = 1,
   blockSchedule: number,
+  scheduledId: string,
+  period = 1,
+  repetitions = 1,
 ) {
   await usingApi(async (api: ApiPromise) => {
     const blockNumber: number | undefined = await getBlockNumber(api);
     const expectedBlockNumber = blockNumber + blockSchedule;
 
     expect(blockNumber).to.be.greaterThan(0);
-    const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
-    const scheduleTx = api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx as any);
+    const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule
+      scheduledId,
+      expectedBlockNumber, 
+      repetitions > 1 ? [period, repetitions] : null, 
+      0, 
+      {value: operationTx as any},
+    );
+
+    const events = await submitTransactionAsync(sender, scheduleTx);
+    expect(getGenericResult(events).success).to.be.true;
+  });
+}
+
+export async function
+scheduleExpectFailure(
+  operationTx: any,
+  sender: IKeyringPair,
+  blockSchedule: number,
+  scheduledId: string,
+  period = 1,
+  repetitions = 1,
+) {
+  await usingApi(async (api: ApiPromise) => {
+    const blockNumber: number | undefined = await getBlockNumber(api);
+    const expectedBlockNumber = blockNumber + blockSchedule;
+
+    expect(blockNumber).to.be.greaterThan(0);
+    const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule
+      scheduledId,
+      expectedBlockNumber, 
+      repetitions <= 1 ? null : [period, repetitions], 
+      0, 
+      {value: operationTx as any},
+    );
+
+    //const events = 
+    await expect(submitTransactionExpectFailAsync(sender, scheduleTx)).to.be.rejected;
+    //expect(getGenericResult(events).success).to.be.false;
+  });
+}
 
-    await submitTransactionAsync(sender, scheduleTx);
+export async function
+scheduleTransferAndWaitExpectSuccess(
+  collectionId: number,
+  tokenId: number,
+  sender: IKeyringPair,
+  recipient: IKeyringPair,
+  value: number | bigint = 1,
+  blockSchedule: number,
+  scheduledId: string,
+) {
+  await usingApi(async (api: ApiPromise) => {
+    await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule, scheduledId);
 
     const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt();
 
-    expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address));
-
-    // sleep for 4 blocks
+    // sleep for n + 1 blocks
     await waitNewBlocks(blockSchedule + 1);
 
     const recipientBalanceAfter = (await api.query.system.account(recipient.address)).data.free.toBigInt();
@@ -967,6 +1020,45 @@
   });
 }
 
+export async function
+scheduleTransferExpectSuccess(
+  collectionId: number,
+  tokenId: number,
+  sender: IKeyringPair,
+  recipient: IKeyringPair,
+  value: number | bigint = 1,
+  blockSchedule: number,
+  scheduledId: string,
+) {
+  await usingApi(async (api: ApiPromise) => {
+    const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
+
+    await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId);
+
+    expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address));
+  });
+}
+
+export async function
+scheduleTransferFundsPeriodicExpectSuccess(
+  amount: bigint,
+  sender: IKeyringPair,
+  recipient: IKeyringPair,
+  blockSchedule: number,
+  scheduledId: string,
+  period: number,
+  repetitions: number,
+) {
+  await usingApi(async (api: ApiPromise) => {
+    const transferTx = api.tx.balances.transfer(recipient.address, amount);
+
+    const balanceBefore = await getFreeBalance(recipient);
+    
+    await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId, period, repetitions);
+
+    expect(await getFreeBalance(recipient)).to.be.equal(balanceBefore);
+  });
+}
 
 export async function
 transferExpectSuccess(