difftreelog
refactor(scheduler) id restricted to 16 bytes [CORE-245]
in: master
1 file changed
pallets/scheduler/src/lib.rsdiffbeforeafterboth1// This file is part of Substrate.23// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! # Scheduler19//! A module for scheduling dispatches.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Module`]24//!25//! ## Overview26//!27//! This module exposes capabilities for scheduling dispatches to occur at a28//! specified block number or at a specified period. These scheduled dispatches29//! may be named or anonymous and may be canceled.30//!31//! **NOTE:** The scheduled calls will be dispatched with the default filter32//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin33//! except root which will get no filter. And not the filter contained in origin34//! use to call `fn schedule`.35//!36//! If a call is scheduled using proxy or whatever mecanism which adds filter,37//! then those filter will not be used when dispatching the schedule call.38//!39//! ## Interface40//!41//! ### Dispatchable Functions42//!43//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a44//! specified block and with a specified priority.45//! * `cancel` - cancel a scheduled dispatch, specified by block number and46//! index.47//! * `schedule_named` - augments the `schedule` interface with an additional48//! `Vec<u8>` parameter that can be used for identification.49//! * `cancel_named` - the named complement to the cancel function.5051// Ensure we're `no_std` when compiling for Wasm.52#![cfg_attr(not(feature = "std"), no_std)]53#![allow(clippy::type_complexity, clippy::boxed_local, clippy::unused_unit)]5455mod benchmarking;56pub mod weights;5758use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow};59use codec::{Encode, Decode, Codec};60use sp_runtime::{61 RuntimeDebug,62 traits::{Zero, One, BadOrigin, Saturating},63};64use frame_support::{65 decl_module, decl_storage, decl_event, decl_error,66 dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter},67 traits::{68 Get,69 schedule::{self, DispatchTime},70 OriginTrait, EnsureOrigin, IsType,71 },72 weights::{GetDispatchInfo, Weight},73};74use frame_system::{self as system, ensure_signed};75pub use weights::WeightInfo;76use up_sponsorship::SponsorshipHandler;77use scale_info::TypeInfo;7879pub const MAX_TASK_ID_LENGTH_IN_BYTES: u32 = 16;8081/// Our pallet's configuration trait. All our types and constants go in here. If the82/// pallet is dependent on specific other pallets, then their configuration traits83/// should be added to our implied traits list.84///85/// `system::Config` should always be included in our implied traits.86/// //87pub trait Config: system::Config {88 /// The overarching event type.89 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;9091 /// The aggregated origin which the dispatch will take.92 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>93 + From<Self::PalletsOrigin>94 + IsType<<Self as system::Config>::Origin>;9596 /// The caller origin, overarching type of all pallets origins.97 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + TypeInfo + Clone + Eq;9899 /// The aggregated call type.100 type Call: Parameter101 + Dispatchable<Origin = <Self as Config>::Origin>102 + GetDispatchInfo103 + From<system::Call<Self>>;104105 /// The maximum weight that may be scheduled per block for any dispatchables of less priority106 /// than `schedule::HARD_DEADLINE`.107 type MaximumWeight: Get<Weight>;108109 /// Required origin to schedule or cancel calls.110 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;111112 /// The maximum number of scheduled calls in the queue for a single block.113 /// Not strictly enforced, but used for weight estimation.114 type MaxScheduledPerBlock: Get<u32>;115116 /// Sponsoring function117 type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;118119 /// Weight information for extrinsics in this pallet.120 type WeightInfo: WeightInfo;121}122123// pub type SelfWeightInfo<T> = <T as system::Config>::WeightInfo;124125/// Just a simple index for naming period tasks.126pub type PeriodicIndex = u32;127/// The location of a scheduled task that can be used to remove it.128pub type TaskAddress<BlockNumber> = (BlockNumber, u32);129130#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]131#[derive(Clone, RuntimeDebug, Encode, Decode)]132struct ScheduledV1<Call, BlockNumber> {133 maybe_id: Option<Vec<u8>>,134 priority: schedule::Priority,135 call: Call,136 maybe_periodic: Option<schedule::Period<BlockNumber>>,137}138139/// Information regarding an item to be executed in the future.140#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]141#[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)]142pub struct ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId> {143 /// The unique identity for this task, if there is one.144 maybe_id: Option<Vec<u8>>,145 /// This task's priority.146 priority: schedule::Priority,147 /// The call to be dispatched.148 call: Call,149 /// If the call is periodic, then this points to the information concerning that.150 maybe_periodic: Option<schedule::Period<BlockNumber>>,151 /// The origin to dispatch the call.152 origin: PalletsOrigin,153 _phantom: PhantomData<AccountId>,154}155156/// The current version of Scheduled struct.157pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =158 ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId>;159160// A value placed in storage that represents the current version of the Scheduler storage.161// This value is used by the `on_runtime_upgrade` logic to determine whether we run162// storage migration logic.163#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]164enum Releases {165 V1,166 V2,167}168169impl Default for Releases {170 fn default() -> Self {171 Releases::V1172 }173}174175#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]176pub struct CallSpec {177 module: u32,178 method: u32,179}180181decl_storage! {182 trait Store for Module<T: Config> as Scheduler {183 /// Items to be executed, indexed by the block number that they should be executed on.184 pub Agenda: map hasher(twox_64_concat) T::BlockNumber185 => Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>;186187 pub SpecAgenda: map hasher(twox_64_concat) T::BlockNumber188 => Vec<Option<CallSpec>>;189190 /// Lookup from identity to the block number and index of the task.191 Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;192193 /// Storage version of the pallet.194 ///195 /// New networks start with last version.196 StorageVersion build(|_| Releases::V2): Releases;197 }198}199200decl_event!(201 pub enum Event<T> where <T as system::Config>::BlockNumber {202 /// Scheduled some task. \[when, index\]203 Scheduled(BlockNumber, u32),204 /// Canceled some task. \[when, index\]205 Canceled(BlockNumber, u32),206 /// Dispatched some task. \[task, id, result\]207 Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),208 }209);210211decl_error! {212 pub enum Error for Module<T: Config> {213 /// Failed to schedule a call214 FailedToSchedule,215 /// Cannot find the scheduled call.216 NotFound,217 /// Given target block number is in the past.218 TargetBlockNumberInPast,219 /// Reschedule failed because it does not change scheduled time.220 RescheduleNoChange,221 }222}223224decl_module! {225 /// Scheduler module declaration.226 pub struct Module<T: Config> for enum Call227 where228 origin: <T as system::Config>::Origin229 {230 type Error = Error<T>;231 fn deposit_event() = default;232233234 /// Anonymously schedule a task.235 ///236 /// # <weight>237 /// - S = Number of already scheduled calls238 /// - Base Weight: 22.29 + .126 * S µs239 /// - DB Weight:240 /// - Read: Agenda241 /// - Write: Agenda242 /// - Will use base weight of 25 which should be good for up to 30 scheduled calls243 /// # </weight>244 #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]245 fn schedule(origin,246 when: T::BlockNumber,247 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,248 priority: schedule::Priority,249 call: Box<<T as Config>::Call>,250 )251 {252 let origin = <T as Config>::Origin::from(origin);253 Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?;254 }255256 /// Cancel an anonymously scheduled task.257 ///258 /// # <weight>259 /// - S = Number of already scheduled calls260 /// - Base Weight: 22.15 + 2.869 * S µs261 /// - DB Weight:262 /// - Read: Agenda263 /// - Write: Agenda, Lookup264 /// - Will use base weight of 100 which should be good for up to 30 scheduled calls265 /// # </weight>266 #[weight = <T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get())]267 fn cancel(origin, when: T::BlockNumber, index: u32) {268 T::ScheduleOrigin::ensure_origin(origin.clone())?;269 let origin = <T as Config>::Origin::from(origin);270 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;271 }272273 /// Schedule a named task.274 ///275 /// # <weight>276 /// - S = Number of already scheduled calls277 /// - Base Weight: 29.6 + .159 * S µs278 /// - DB Weight:279 /// - Read: Agenda, Lookup280 /// - Write: Agenda, Lookup281 /// - Will use base weight of 35 which should be good for more than 30 scheduled calls282 /// # </weight>283 #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]284 fn schedule_named(origin,285 id: Vec<u8>,286 when: T::BlockNumber,287 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,288 priority: schedule::Priority,289 call: Box<<T as Config>::Call>,290 ) {291 T::ScheduleOrigin::ensure_origin(origin.clone())?;292 let origin = <T as Config>::Origin::from(origin);293 Self::do_schedule_named(294 id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call295 )?;296 }297298 /// Cancel a named scheduled task.299 ///300 /// # <weight>301 /// - S = Number of already scheduled calls302 /// - Base Weight: 24.91 + 2.907 * S µs303 /// - DB Weight:304 /// - Read: Agenda, Lookup305 /// - Write: Agenda, Lookup306 /// - Will use base weight of 100 which should be good for up to 30 scheduled calls307 /// # </weight>308 #[weight = <T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())]309 fn cancel_named(origin, id: Vec<u8>) {310 T::ScheduleOrigin::ensure_origin(origin.clone())?;311 let origin = <T as Config>::Origin::from(origin);312 Self::do_cancel_named(Some(origin.caller().clone()), id)?;313 }314315 /// Anonymously schedule a task after a delay.316 ///317 /// # <weight>318 /// Same as [`schedule`].319 /// # </weight>320 #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]321 fn schedule_after(origin,322 after: T::BlockNumber,323 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,324 priority: schedule::Priority,325 call: Box<<T as Config>::Call>,326 ) {327 T::ScheduleOrigin::ensure_origin(origin.clone())?;328 let origin = <T as Config>::Origin::from(origin);329 Self::do_schedule(330 DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call331 )?;332 }333334 /// Schedule a named task after a delay.335 ///336 /// # <weight>337 /// Same as [`schedule_named`].338 /// # </weight>339 #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]340 fn schedule_named_after(origin,341 id: Vec<u8>,342 after: T::BlockNumber,343 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,344 priority: schedule::Priority,345 call: Box<<T as Config>::Call>,346 ) {347 T::ScheduleOrigin::ensure_origin(origin.clone())?;348 let origin = <T as Config>::Origin::from(origin);349 Self::do_schedule_named(350 id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call351 )?;352 }353354 /// Execute the scheduled calls355 ///356 /// # <weight>357 /// - S = Number of already scheduled calls358 /// - N = Named scheduled calls359 /// - P = Periodic Calls360 /// - Base Weight: 9.243 + 23.45 * S µs361 /// - DB Weight:362 /// - Read: Agenda + Lookup * N + Agenda(Future) * P363 /// - Write: Agenda + Lookup * N + Agenda(future) * P364 /// # </weight>365 fn on_initialize(now: T::BlockNumber) -> Weight {366 let limit = T::MaximumWeight::get();367 let mut queued = Agenda::<T>::take(now).into_iter()368 .enumerate()369 .filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))370 .collect::<Vec<_>>();371 if queued.len() as u32 > T::MaxScheduledPerBlock::get() {372 log::warn!(373 target: "runtime::scheduler",374 "Warning: This block has more items queued in Scheduler than \375 expected from the runtime configuration. An update might be needed."376 );377 }378 queued.sort_by_key(|(_, s)| s.priority);379 let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)380 let mut total_weight: Weight = 0;381 queued.into_iter()382 .enumerate()383 .scan(base_weight, |cumulative_weight, (order, (index, s))| {384 *cumulative_weight = cumulative_weight385 .saturating_add(s.call.get_dispatch_info().weight);386387 let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(388 s.origin.clone()389 ).into();390391 if ensure_signed(origin).is_ok() {392 // AccountData for inner call origin accountdata.393 *cumulative_weight = cumulative_weight394 .saturating_add(T::DbWeight::get().reads_writes(1, 1));395 }396397 if s.maybe_id.is_some() {398 // Remove/Modify Lookup399 *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1));400 }401 if s.maybe_periodic.is_some() {402 // Read/Write Agenda for future block403 *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));404 }405406 Some((order, index, *cumulative_weight, s))407 })408 .filter_map(|(order, index, cumulative_weight, mut s)| {409 // We allow a scheduled call if any is true:410 // - It's priority is `HARD_DEADLINE`411 // - It does not push the weight past the limit.412 // - It is the first item in the schedule413 if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 {414415 let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(416 s.origin.clone()417 ).into();418 let sender = ensure_signed(origin).unwrap_or_default();419 let who_will_pay = T::SponsorshipHandler::get_sponsor(&sender, &s.call).unwrap_or(sender);420 let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));421 let r = s.call.clone().dispatch(sponsor.into());422 let maybe_id = s.maybe_id.clone();423 if let Some((period, count)) = s.maybe_periodic {424 if count > 1 {425 s.maybe_periodic = Some((period, count - 1));426 } else {427 s.maybe_periodic = None;428 }429 let next = now + period;430 // If scheduled is named, place it's information in `Lookup`431 if let Some(ref id) = s.maybe_id {432 let next_index = Agenda::<T>::decode_len(now + period).unwrap_or(0);433 Lookup::<T>::insert(id, (next, next_index as u32));434 }435 Agenda::<T>::append(next, Some(s));436 } else if let Some(ref id) = s.maybe_id {437 Lookup::<T>::remove(id);438 }439 Self::deposit_event(RawEvent::Dispatched(440 (now, index),441 maybe_id,442 r.map(|_| ()).map_err(|e| e.error)443 ));444 total_weight = cumulative_weight;445 None446 } else {447 Some(Some(s))448 }449 })450 .for_each(|unused| {451 let next = now + One::one();452 Agenda::<T>::append(next, unused);453 });454455 total_weight456 }457 }458}459460impl<T: Config> Module<T> {461 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {462 let now = frame_system::Pallet::<T>::block_number();463464 let when = match when {465 DispatchTime::At(x) => x,466 // The current block has already completed it's scheduled tasks, so467 // Schedule the task at lest one block after this current block.468 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),469 };470471 if when <= now {472 return Err(Error::<T>::TargetBlockNumberInPast.into());473 }474475 Ok(when)476 }477478 fn do_schedule(479 when: DispatchTime<T::BlockNumber>,480 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,481 priority: schedule::Priority,482 origin: T::PalletsOrigin,483 call: <T as Config>::Call,484 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {485 let when = Self::resolve_time(when)?;486487 // sanitize maybe_periodic488 let maybe_periodic = maybe_periodic489 .filter(|p| p.1 > 1 && !p.0.is_zero())490 // Remove one from the number of repetitions since we will schedule one now.491 .map(|(p, c)| (p, c - 1));492 let s = Some(Scheduled {493 maybe_id: None,494 priority,495 call,496 maybe_periodic,497 origin,498 _phantom: PhantomData::<T::AccountId>::default(),499 });500 Agenda::<T>::append(when, s);501 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;502 if index > T::MaxScheduledPerBlock::get() {503 log::warn!(504 target: "runtime::scheduler",505 "Warning: There are more items queued in the Scheduler than \506 expected from the runtime configuration. An update might be needed.",507 );508 }509 Self::deposit_event(RawEvent::Scheduled(when, index));510511 Ok((when, index))512 }513514 fn do_cancel(515 origin: Option<T::PalletsOrigin>,516 (when, index): TaskAddress<T::BlockNumber>,517 ) -> Result<(), DispatchError> {518 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {519 agenda.get_mut(index as usize).map_or(520 Ok(None),521 |s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {522 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {523 if *o != s.origin {524 return Err(BadOrigin.into());525 }526 };527 Ok(s.take())528 },529 )530 })?;531 if let Some(s) = scheduled {532 if let Some(id) = s.maybe_id {533 Lookup::<T>::remove(id);534 }535 Self::deposit_event(RawEvent::Canceled(when, index));536 Ok(())537 } else {538 Err(Error::<T>::NotFound.into())539 }540 }541542 fn do_reschedule(543 (when, index): TaskAddress<T::BlockNumber>,544 new_time: DispatchTime<T::BlockNumber>,545 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {546 let new_time = Self::resolve_time(new_time)?;547548 if new_time == when {549 return Err(Error::<T>::RescheduleNoChange.into());550 }551552 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {553 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;554 let task = task.take().ok_or(Error::<T>::NotFound)?;555 Agenda::<T>::append(new_time, Some(task));556 Ok(())557 })?;558559 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;560 Self::deposit_event(RawEvent::Canceled(when, index));561 Self::deposit_event(RawEvent::Scheduled(new_time, new_index));562563 Ok((new_time, new_index))564 }565566 fn do_schedule_named(567 id: Vec<u8>,568 when: DispatchTime<T::BlockNumber>,569 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,570 priority: schedule::Priority,571 origin: T::PalletsOrigin,572 call: <T as Config>::Call,573 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {574 // ensure id length does not exceed expectations & is unique575 if id.len() > MAX_TASK_ID_LENGTH_IN_BYTES.try_into().unwrap()576 || Lookup::<T>::contains_key(&id)577 {578 return Err(Error::<T>::FailedToSchedule.into());579 }580581 let when = Self::resolve_time(when)?;582583 // sanitize maybe_periodic584 let maybe_periodic = maybe_periodic585 .filter(|p| p.1 > 1 && !p.0.is_zero())586 // Remove one from the number of repetitions since we will schedule one now.587 .map(|(p, c)| (p, c - 1));588589 let s = Scheduled {590 maybe_id: Some(id.clone()),591 priority,592 call,593 maybe_periodic,594 origin,595 _phantom: Default::default(),596 };597 Agenda::<T>::append(when, Some(s));598 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;599 if index > T::MaxScheduledPerBlock::get() {600 log::warn!(601 target: "runtime::scheduler",602 "Warning: There are more items queued in the Scheduler than \603 expected from the runtime configuration. An update might be needed.",604 );605 }606 let address = (when, index);607 Lookup::<T>::insert(&id, &address);608 Self::deposit_event(RawEvent::Scheduled(when, index));609610 Ok(address)611 }612613 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: Vec<u8>) -> DispatchResult {614 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {615 if let Some((when, index)) = lookup.take() {616 let i = index as usize;617 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {618 if let Some(s) = agenda.get_mut(i) {619 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {620 if *o != s.origin {621 return Err(BadOrigin.into());622 }623 }624 *s = None;625 }626 Ok(())627 })?;628 Self::deposit_event(RawEvent::Canceled(when, index));629 Ok(())630 } else {631 Err(Error::<T>::NotFound.into())632 }633 })634 }635636 fn do_reschedule_named(637 id: Vec<u8>,638 new_time: DispatchTime<T::BlockNumber>,639 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {640 let new_time = Self::resolve_time(new_time)?;641642 Lookup::<T>::try_mutate_exists(643 id,644 |lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {645 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;646647 if new_time == when {648 return Err(Error::<T>::RescheduleNoChange.into());649 }650651 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {652 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;653 let task = task.take().ok_or(Error::<T>::NotFound)?;654 Agenda::<T>::append(new_time, Some(task));655656 Ok(())657 })?;658659 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;660 Self::deposit_event(RawEvent::Canceled(when, index));661 Self::deposit_event(RawEvent::Scheduled(new_time, new_index));662663 *lookup = Some((new_time, new_index));664665 Ok((new_time, new_index))666 },667 )668 }669}670671impl<T: Config> schedule::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>672 for Module<T>673{674 type Address = TaskAddress<T::BlockNumber>;675676 fn schedule(677 when: DispatchTime<T::BlockNumber>,678 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,679 priority: schedule::Priority,680 origin: T::PalletsOrigin,681 call: <T as Config>::Call,682 ) -> Result<Self::Address, DispatchError> {683 Self::do_schedule(when, maybe_periodic, priority, origin, call)684 }685686 fn cancel((when, index): Self::Address) -> Result<(), ()> {687 Self::do_cancel(None, (when, index)).map_err(|_| ())688 }689690 fn reschedule(691 address: Self::Address,692 when: DispatchTime<T::BlockNumber>,693 ) -> Result<Self::Address, DispatchError> {694 Self::do_reschedule(address, when)695 }696697 fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {698 Agenda::<T>::get(when)699 .get(index as usize)700 .ok_or(())701 .map(|_| when)702 }703}704705impl<T: Config> schedule::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>706 for Module<T>707{708 type Address = TaskAddress<T::BlockNumber>;709710 fn schedule_named(711 id: Vec<u8>,712 when: DispatchTime<T::BlockNumber>,713 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,714 priority: schedule::Priority,715 origin: T::PalletsOrigin,716 call: <T as Config>::Call,717 ) -> Result<Self::Address, ()> {718 Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ())719 }720721 fn cancel_named(id: Vec<u8>) -> Result<(), ()> {722 Self::do_cancel_named(None, id).map_err(|_| ())723 }724725 fn reschedule_named(726 id: Vec<u8>,727 when: DispatchTime<T::BlockNumber>,728 ) -> Result<Self::Address, DispatchError> {729 Self::do_reschedule_named(id, when)730 }731732 fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {733 Lookup::<T>::get(id)734 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))735 .ok_or(())736 }737}738739#[cfg(test)]740#[allow(clippy::from_over_into)]741mod tests {742 use super::*;743744 use frame_support::{745 ord_parameter_types, parameter_types, traits::Contains, weights::constants::RocksDbWeight,746 };747 use sp_core::H256;748 use sp_runtime::{749 Perbill,750 testing::Header,751 traits::{BlakeTwo256, IdentityLookup},752 };753 use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy};754 use crate as scheduler;755756 mod logger {757 use super::*;758 use std::cell::RefCell;759760 thread_local! {761 static LOG: RefCell<Vec<(OriginCaller, u32)>> = RefCell::new(Vec::new());762 }763 pub trait Config: system::Config {764 type Event: From<Event> + Into<<Self as system::Config>::Event>;765 }766 decl_event! {767 pub enum Event {768 Logged(u32, Weight),769 }770 }771 decl_module! {772 pub struct Module<T: Config> for enum Call773 where774 origin: <T as system::Config>::Origin,775 <T as system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>776 {777 fn deposit_event() = default;778779 #[weight = *weight]780 fn log(origin, i: u32, weight: Weight) {781 Self::deposit_event(Event::Logged(i, weight));782 LOG.with(|log| {783 log.borrow_mut().push((origin.caller().clone(), i));784 })785 }786787 #[weight = *weight]788 fn log_without_filter(origin, i: u32, weight: Weight) {789 Self::deposit_event(Event::Logged(i, weight));790 LOG.with(|log| {791 log.borrow_mut().push((origin.caller().clone(), i));792 })793 }794 }795 }796 }797798 type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;799 // todo check current cumulus implementation, add changes accordingly, do something with signedExtra and (un)checked extrinsic (integral to Scheduler?)800 // todo to include pallet in runtime, not only uncomment but also (implement?) trait801 type Block = frame_system::mocking::MockBlock<Test>;802803 frame_support::construct_runtime!(804 pub enum Test where805 Block = Block,806 NodeBlock = Block,807 UncheckedExtrinsic = UncheckedExtrinsic,808 {809 System: frame_system::{Pallet, Call, Config, Storage, Event<T>},810 Logger: logger::{Pallet, Call, Event},811 Scheduler: scheduler::{Pallet, Call, Storage, Event<T>},812 }813 );814815 // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used.816 pub struct BaseFilter;817 impl Contains<Call> for BaseFilter {818 fn contains(call: &Call) -> bool {819 !matches!(call, Call::Logger(logger::Call::log { .. }))820 }821 }822823 parameter_types! {824 pub const BlockHashCount: u64 = 250;825 pub BlockWeights: frame_system::limits::BlockWeights =826 frame_system::limits::BlockWeights::simple_max(2_000_000_000_000);827 }828 impl system::Config for Test {829 type BaseCallFilter = BaseFilter;830 type BlockWeights = ();831 type BlockLength = ();832 type DbWeight = RocksDbWeight;833 type Origin = Origin;834 type Call = Call;835 type Index = u64;836 type BlockNumber = u64;837 type Hash = H256;838 type Hashing = BlakeTwo256;839 type AccountId = u64;840 type Lookup = IdentityLookup<Self::AccountId>;841 type Header = Header;842 type Event = Event;843 type BlockHashCount = BlockHashCount;844 type Version = ();845 type PalletInfo = PalletInfo;846 type AccountData = ();847 type OnNewAccount = ();848 type OnKilledAccount = ();849 type SystemWeightInfo = ();850 type SS58Prefix = ();851 type OnSetCode = ();852 }853 impl logger::Config for Test {854 type Event = Event;855 }856 parameter_types! {857 pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;858 pub const MaxScheduledPerBlock: u32 = 10;859 }860 ord_parameter_types! {861 pub const One: u64 = 1;862 }863864 impl Config for Test {865 type Event = Event;866 type Origin = Origin;867 type PalletsOrigin = OriginCaller;868 type Call = Call;869 type MaximumWeight = MaximumSchedulerWeight;870 type ScheduleOrigin = EnsureOneOf<u64, EnsureRoot<u64>, EnsureSignedBy<One, u64>>;871 type MaxScheduledPerBlock = MaxScheduledPerBlock;872 type WeightInfo = ();873 type SponsorshipHandler = ();874 }875}