--- a/Cargo.lock +++ b/Cargo.lock @@ -5378,12 +5378,14 @@ "pallet-structure", "pallet-sudo", "pallet-template-transaction-payment", + "pallet-test-utils", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-unique", "pallet-unique-scheduler", + "pallet-unique-scheduler-v2", "pallet-xcm", "parachain-info", "parity-scale-codec 3.2.1", @@ -6702,6 +6704,17 @@ ] [[package]] +name = "pallet-test-utils" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-unique-scheduler-v2", + "parity-scale-codec 3.2.1", + "scale-info", +] + +[[package]] name = "pallet-timestamp" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30#a3ed0119c45cdd0d571ad34e5b3ee7518c8cef8d" @@ -6841,6 +6854,24 @@ ] [[package]] +name = "pallet-unique-scheduler-v2" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-preimage", + "parity-scale-codec 3.2.1", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "substrate-test-utils", +] + +[[package]] name = "pallet-utility" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30#a3ed0119c45cdd0d571ad34e5b3ee7518c8cef8d" --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ 'runtime/tests', ] default-members = ['node/*', 'runtime/opal'] + [profile.release] panic = 'unwind' --- /dev/null +++ b/pallets/scheduler-v2/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-unique-scheduler-v2" +version = "0.1.0" +authors = ["Unique Network "] +edition = "2021" +license = "GPLv3" +homepage = "https://unique.network" +repository = "https://github.com/UniqueNetwork/unique-chain" +description = "Unique Scheduler pallet" +readme = "README.md" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-io = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-std = { version = "4.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } + +[dev-dependencies] +pallet-preimage = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +substrate-test-utils = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", +] +try-runtime = ["frame-support/try-runtime"] --- /dev/null +++ b/pallets/scheduler-v2/src/benchmarking.rs @@ -0,0 +1,349 @@ +// 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 . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Scheduler pallet benchmarking. + +use super::*; +use frame_benchmarking::{account, benchmarks}; +use frame_support::{ + ensure, + traits::{schedule::Priority, PreimageRecipient}, +}; +use frame_system::RawOrigin; +use sp_std::{prelude::*, vec}; +use sp_io::hashing::blake2_256; + +use crate::{Pallet as Scheduler, ScheduledCall, EncodedCall}; +use frame_system::Call as SystemCall; + +const SEED: u32 = 0; + +const BLOCK_NUMBER: u32 = 2; + +type SystemOrigin = ::Origin; + +/// Add `n` items to the schedule. +/// +/// For `resolved`: +/// - ` +/// - `None`: aborted (hash without preimage) +/// - `Some(true)`: hash resolves into call if possible, plain call otherwise +/// - `Some(false)`: plain call +fn fill_schedule(when: T::BlockNumber, n: u32) -> Result<(), &'static str> { + let t = DispatchTime::At(when); + let origin: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); + for i in 0..n { + let call = make_call::(None); + let period = Some(((i + 100).into(), 100)); + let name = u32_to_name(i); + Scheduler::::do_schedule_named(name, t, period, 0, origin.clone(), call)?; + } + ensure!( + Agenda::::get(when).len() == n as usize, + "didn't fill schedule" + ); + Ok(()) +} + +fn u32_to_name(i: u32) -> TaskName { + i.using_encoded(blake2_256) +} + +fn make_task( + periodic: bool, + named: bool, + signed: bool, + maybe_lookup_len: Option, + priority: Priority, +) -> ScheduledOf { + let call = make_call::(maybe_lookup_len); + let maybe_periodic = match periodic { + true => Some((100u32.into(), 100)), + false => None, + }; + let maybe_id = match named { + true => Some(u32_to_name(0)), + false => None, + }; + let origin = make_origin::(signed); + Scheduled { + maybe_id, + priority, + call, + maybe_periodic, + origin, + _phantom: PhantomData, + } +} + +fn bounded(len: u32) -> Option> { + let call = <::Call>::from(SystemCall::remark { + remark: vec![0; len as usize], + }); + ScheduledCall::new(call).ok() +} + +fn make_call(maybe_lookup_len: Option) -> ScheduledCall { + let bound = EncodedCall::bound() as u32; + let mut len = match maybe_lookup_len { + Some(len) => { + len.min(>::MaxSize::get() - 2) + .max(bound) - 3 + } + None => bound.saturating_sub(4), + }; + + loop { + let c = match bounded::(len) { + Some(x) => x, + None => { + len -= 1; + continue; + } + }; + if c.lookup_needed() == maybe_lookup_len.is_some() { + break c; + } + if maybe_lookup_len.is_some() { + len += 1; + } else { + if len > 0 { + len -= 1; + } else { + break c; + } + } + } +} + +fn make_origin(signed: bool) -> ::PalletsOrigin { + match signed { + true => frame_system::RawOrigin::Signed(account("origin", 0, SEED)).into(), + false => frame_system::RawOrigin::Root.into(), + } +} + +fn dummy_counter() -> WeightCounter { + WeightCounter { + used: Weight::zero(), + limit: Weight::MAX, + } +} + +benchmarks! { + // `service_agendas` when no work is done. + service_agendas_base { + let now = T::BlockNumber::from(BLOCK_NUMBER); + IncompleteSince::::put(now - One::one()); + }: { + Scheduler::::service_agendas(&mut dummy_counter(), now, 0); + } verify { + assert_eq!(IncompleteSince::::get(), Some(now - One::one())); + } + + // `service_agenda` when no work is done. + service_agenda_base { + let now = BLOCK_NUMBER.into(); + let s in 0 .. T::MaxScheduledPerBlock::get(); + fill_schedule::(now, s)?; + let mut executed = 0; + }: { + Scheduler::::service_agenda(&mut dummy_counter(), &mut executed, now, now, 0); + } verify { + assert_eq!(executed, 0); + } + + // `service_task` when the task is a non-periodic, non-named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_base { + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, false, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + //assert_eq!(result, Ok(())); + } + + // `service_task` when the task is a non-periodic, non-named, fetched call (with a known + // preimage length) and which is not dispatched (e.g. due to being overweight). + service_task_fetched { + let s in (EncodedCall::bound() as u32) .. (>::MaxSize::get()); + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, false, false, Some(s), 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `service_task` when the task is a non-periodic, named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_named { + let now = BLOCK_NUMBER.into(); + let task = make_task::(false, true, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `service_task` when the task is a periodic, non-named, non-fetched call which is not + // dispatched (e.g. due to being overweight). + service_task_periodic { + let now = BLOCK_NUMBER.into(); + let task = make_task::(true, false, false, None, 0); + // prevent any tasks from actually being executed as we only want the surrounding weight. + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + }: { + let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); + } verify { + } + + // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight. + execute_dispatch_signed { + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let origin = make_origin::(true); + let call = T::Preimages::realize(&make_call::(None)).unwrap().0; + }: { + assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); + } + verify { + } + + // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight. + execute_dispatch_unsigned { + let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let origin = make_origin::(false); + let call = T::Preimages::realize(&make_call::(None)).unwrap().0; + }: { + assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); + } + verify { + } + + schedule { + let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); + let when = BLOCK_NUMBER.into(); + let periodic = Some((T::BlockNumber::one(), 100)); + let priority = Some(0); + // Essentially a no-op call. + let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); + + fill_schedule::(when, s)?; + }: _(RawOrigin::Root, when, periodic, priority, call) + verify { + ensure!( + Agenda::::get(when).len() == (s + 1) as usize, + "didn't add to schedule" + ); + } + + cancel { + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + assert_eq!(Agenda::::get(when).len(), s as usize); + let schedule_origin = T::ScheduleOrigin::successful_origin(); + }: _>(schedule_origin, when, 0) + verify { + ensure!( + Lookup::::get(u32_to_name(0)).is_none(), + "didn't remove from lookup" + ); + // Removed schedule is NONE + ensure!( + Agenda::::get(when)[0].is_none(), + "didn't remove from schedule" + ); + } + + schedule_named { + let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); + let id = u32_to_name(s); + let when = BLOCK_NUMBER.into(); + let periodic = Some((T::BlockNumber::one(), 100)); + let priority = Some(0); + // Essentially a no-op call. + let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); + + fill_schedule::(when, s)?; + }: _(RawOrigin::Root, id, when, periodic, priority, call) + verify { + ensure!( + Agenda::::get(when).len() == (s + 1) as usize, + "didn't add to schedule" + ); + } + + cancel_named { + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + }: _(RawOrigin::Root, u32_to_name(0)) + verify { + ensure!( + Lookup::::get(u32_to_name(0)).is_none(), + "didn't remove from lookup" + ); + // Removed schedule is NONE + ensure!( + Agenda::::get(when)[0].is_none(), + "didn't remove from schedule" + ); + } + + change_named_priority { + let origin: RawOrigin = frame_system::RawOrigin::Root; + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + let idx = s - 1; + let id = u32_to_name(idx); + let priority = 42; + fill_schedule::(when, s)?; + }: _(origin, id, priority) + verify { + ensure!( + Agenda::::get(when)[idx as usize].clone().unwrap().priority == priority, + "didn't change the priority" + ); + } + + impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); +} --- /dev/null +++ b/pallets/scheduler-v2/src/lib.rs @@ -0,0 +1,1102 @@ +// 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 . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Scheduler +//! A Pallet for scheduling dispatches. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! ## Overview +//! +//! 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. +//! +//! **NOTE:** The scheduled calls will be dispatched with the default filter +//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin +//! except root which will get no filter. And not the filter contained in origin +//! use to call `fn schedule`. +//! +//! If a call is scheduled using proxy or whatever mecanism which adds filter, +//! then those filter will not be used when dispatching the schedule call. +//! +//! ## Interface +//! +//! ### 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` 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)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::{ + DispatchError, DispatchResult, Dispatchable, GetDispatchInfo, Parameter, PostDispatchInfo, + }, + traits::{ + schedule::{self, DispatchTime, LOWEST_PRIORITY}, + EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp, StorageVersion, PreimageRecipient, + ConstU32, UnfilteredDispatchable, + }, + weights::Weight, + unsigned::TransactionValidityError, +}; + +use frame_system::{self as system}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{BadOrigin, One, Saturating, Zero, Hash}, + BoundedVec, RuntimeDebug, DispatchErrorWithPostInfo, +}; +use sp_core::H160; +use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; +pub use weights::WeightInfo; + +pub use pallet::*; + +/// 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, u32); + +pub type EncodedCall = BoundedVec>; + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub enum ScheduledCall { + Inline(EncodedCall), + PreimageLookup { hash: T::Hash, unbounded_len: u32 }, +} + +impl ScheduledCall { + pub fn new(call: ::RuntimeCall) -> Result { + let encoded = call.encode(); + let len = encoded.len(); + + match EncodedCall::try_from(encoded.clone()) { + Ok(bounded) => Ok(Self::Inline(bounded)), + Err(_) => { + let hash = ::Hashing::hash_of(&encoded); + ::Preimages::note_preimage( + encoded + .try_into() + .map_err(|_| >::TooBigScheduledCall)?, + ); + + Ok(Self::PreimageLookup { + hash, + unbounded_len: len as u32, + }) + } + } + } + + /// The maximum length of the lookup that is needed to peek `Self`. + pub fn lookup_len(&self) -> Option { + match self { + Self::Inline(..) => None, + Self::PreimageLookup { unbounded_len, .. } => Some(*unbounded_len), + } + } + + /// Returns whether the image will require a lookup to be peeked. + pub fn lookup_needed(&self) -> bool { + match self { + Self::Inline(_) => false, + Self::PreimageLookup { .. } => true, + } + } + + fn decode(mut data: &[u8]) -> Result<::RuntimeCall, DispatchError> { + ::RuntimeCall::decode(&mut data) + .map_err(|_| >::ScheduledCallCorrupted.into()) + } +} + +pub trait SchedulerPreimages: PreimageRecipient { + fn drop(call: &ScheduledCall); + + fn peek( + call: &ScheduledCall, + ) -> Result<(::RuntimeCall, Option), DispatchError>; + + /// Convert the given scheduled `call` value back into its original instance. If successful, + /// `drop` any data backing it. This will not break the realisability of independently + /// created instances of `ScheduledCall` which happen to have identical data. + fn realize( + call: &ScheduledCall, + ) -> Result<(::RuntimeCall, Option), DispatchError>; +} + +impl> SchedulerPreimages for PP { + fn drop(call: &ScheduledCall) { + match call { + ScheduledCall::Inline(_) => {} + ScheduledCall::PreimageLookup { hash, .. } => Self::unrequest_preimage(hash), + } + } + + fn peek( + call: &ScheduledCall, + ) -> Result<(::RuntimeCall, Option), DispatchError> { + match call { + ScheduledCall::Inline(data) => Ok((ScheduledCall::::decode(data)?, None)), + ScheduledCall::PreimageLookup { + hash, + unbounded_len, + } => { + let (preimage, len) = Self::get_preimage(hash) + .ok_or(>::PreimageNotFound) + .map(|preimage| (preimage, *unbounded_len))?; + + Ok((ScheduledCall::::decode(preimage.as_slice())?, Some(len))) + } + } + } + + fn realize( + call: &ScheduledCall, + ) -> Result<(::RuntimeCall, Option), DispatchError> { + let r = Self::peek(call)?; + Self::drop(call); + Ok(r) + } +} + +pub enum ScheduledEnsureOriginSuccess { + Root, + Signed(AccountId), +} + +pub type TaskName = [u8; 32]; + +/// Information regarding an item to be executed in the future. +#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct Scheduled { + /// The unique identity for this task, if there is one. + maybe_id: Option, + + /// This task's priority. + priority: schedule::Priority, + + /// The call to be dispatched. + call: Call, + + /// If the call is periodic, then this points to the information concerning that. + maybe_periodic: Option>, + + /// The origin with which to dispatch the call. + origin: PalletsOrigin, + _phantom: PhantomData, +} + +pub type ScheduledOf = Scheduled< + TaskName, + ScheduledCall, + ::BlockNumber, + ::PalletsOrigin, + ::AccountId, +>; + +struct WeightCounter { + used: Weight, + limit: Weight, +} + +impl WeightCounter { + fn check_accrue(&mut self, w: Weight) -> bool { + let test = self.used.saturating_add(w); + if test.any_gt(self.limit) { + false + } else { + self.used = test; + true + } + } + + fn can_accrue(&mut self, w: Weight) -> bool { + self.used.saturating_add(w).all_lte(self.limit) + } +} + +pub(crate) trait MarginalWeightInfo: WeightInfo { + fn service_task(maybe_lookup_len: Option, named: bool, periodic: bool) -> Weight { + let base = Self::service_task_base(); + let mut total = match maybe_lookup_len { + None => base, + Some(l) => Self::service_task_fetched(l as u32), + }; + if named { + total.saturating_accrue(Self::service_task_named().saturating_sub(base)); + } + if periodic { + total.saturating_accrue(Self::service_task_periodic().saturating_sub(base)); + } + total + } +} + +impl MarginalWeightInfo for T {} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*}; + use system::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The aggregated origin which the dispatch will take. + type RuntimeOrigin: OriginTrait + + From + + IsType<::RuntimeOrigin> + + Clone; + + /// The caller origin, overarching type of all pallets origins. + type PalletsOrigin: From> + + Codec + + Clone + + Eq + + TypeInfo + + MaxEncodedLen; + + /// The aggregated call type. + type RuntimeCall: Parameter + + Dispatchable< + RuntimeOrigin = ::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + UnfilteredDispatchable::RuntimeOrigin> + + GetDispatchInfo + + From>; + + /// The maximum weight that may be scheduled per block for any dispatchables. + #[pallet::constant] + type MaximumWeight: Get; + + /// Required origin to schedule or cancel calls. + type ScheduleOrigin: EnsureOrigin< + ::RuntimeOrigin, + Success = ScheduledEnsureOriginSuccess, + >; + + /// 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. + /// + /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can + /// be used. This will only check if two given origins are equal. + type OriginPrivilegeCmp: PrivilegeCmp; + + /// The maximum number of scheduled calls in the queue for a single block. + #[pallet::constant] + type MaxScheduledPerBlock: Get; + + /// 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 Preimages: SchedulerPreimages; + + /// The helper type used for custom transaction fee logic. + type CallExecutor: DispatchCall; + + /// Required origin to set/change calls' priority. + type PrioritySetOrigin: EnsureOrigin<::RuntimeOrigin>; + } + + #[pallet::storage] + pub type IncompleteSince = StorageValue<_, T::BlockNumber>; + + /// Items to be executed, indexed by the block number that they should be executed on. + #[pallet::storage] + pub type Agenda = StorageMap< + _, + Twox64Concat, + T::BlockNumber, + BoundedVec>, T::MaxScheduledPerBlock>, + ValueQuery, + >; + + /// Lookup from a name to the block number and index of the task. + #[pallet::storage] + pub(crate) type Lookup = + StorageMap<_, Twox64Concat, TaskName, TaskAddress>; + + /// Events type. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Scheduled some task. + Scheduled { when: T::BlockNumber, index: u32 }, + /// Canceled some task. + Canceled { when: T::BlockNumber, index: u32 }, + /// Dispatched some task. + Dispatched { + task: TaskAddress, + id: Option<[u8; 32]>, + result: DispatchResult, + }, + /// Scheduled task's priority has changed + PriorityChanged { + when: T::BlockNumber, + index: u32, + priority: schedule::Priority, + }, + /// The call for the provided hash was not found so the task has been aborted. + CallUnavailable { + task: TaskAddress, + id: Option<[u8; 32]>, + }, + /// The given task was unable to be renewed since the agenda is full at that block. + PeriodicFailed { + task: TaskAddress, + id: Option<[u8; 32]>, + }, + /// The given task can never be executed since it is overweight. + PermanentlyOverweight { + task: TaskAddress, + id: Option<[u8; 32]>, + }, + } + + #[pallet::error] + pub enum Error { + /// Failed to schedule a call + FailedToSchedule, + /// There is no place for a new task in the agenda + AgendaIsExhausted, + /// Scheduled call is corrupted + ScheduledCallCorrupted, + /// Scheduled call preimage is not found + PreimageNotFound, + /// Scheduled call is too big + TooBigScheduledCall, + /// Cannot find the scheduled call. + NotFound, + /// Given target block number is in the past. + TargetBlockNumberInPast, + /// Attempt to use a non-named function on a named task. + Named, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Execute the scheduled calls + fn on_initialize(now: T::BlockNumber) -> Weight { + let mut weight_counter = WeightCounter { + used: Weight::zero(), + limit: T::MaximumWeight::get(), + }; + Self::service_agendas(&mut weight_counter, now, u32::max_value()); + weight_counter.used + } + } + + #[pallet::call] + impl Pallet { + /// Anonymously schedule a task. + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule( + origin: OriginFor, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + + let origin = ::RuntimeOrigin::from(origin); + Self::do_schedule( + DispatchTime::At(when), + maybe_periodic, + priority.unwrap_or(LOWEST_PRIORITY), + origin.caller().clone(), + >::new(*call)?, + )?; + Ok(()) + } + + /// Cancel an anonymously scheduled task. + #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] + pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_cancel(Some(origin.caller().clone()), (when, index))?; + Ok(()) + } + + /// Schedule a named task. + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named( + origin: OriginFor, + id: TaskName, + when: T::BlockNumber, + maybe_periodic: Option>, + priority: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + + let origin = ::RuntimeOrigin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::At(when), + maybe_periodic, + priority.unwrap_or(LOWEST_PRIORITY), + origin.caller().clone(), + >::new(*call)?, + )?; + Ok(()) + } + + /// Cancel a named scheduled task. + #[pallet::weight(::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))] + pub fn cancel_named(origin: OriginFor, id: TaskName) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_cancel_named(Some(origin.caller().clone()), id)?; + Ok(()) + } + + /// Anonymously schedule a task after a delay. + /// + /// # + /// Same as [`schedule`]. + /// # + #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] + pub fn schedule_after( + origin: OriginFor, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + + let origin = ::RuntimeOrigin::from(origin); + Self::do_schedule( + DispatchTime::After(after), + maybe_periodic, + priority.unwrap_or(LOWEST_PRIORITY), + origin.caller().clone(), + >::new(*call)?, + )?; + Ok(()) + } + + /// Schedule a named task after a delay. + /// + /// # + /// Same as [`schedule_named`](Self::schedule_named). + /// # + #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] + pub fn schedule_named_after( + origin: OriginFor, + id: TaskName, + after: T::BlockNumber, + maybe_periodic: Option>, + priority: Option, + call: Box<::RuntimeCall>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + + let origin = ::RuntimeOrigin::from(origin); + Self::do_schedule_named( + id, + DispatchTime::After(after), + maybe_periodic, + priority.unwrap_or(LOWEST_PRIORITY), + origin.caller().clone(), + >::new(*call)?, + )?; + Ok(()) + } + + #[pallet::weight(::WeightInfo::change_named_priority(T::MaxScheduledPerBlock::get()))] + pub fn change_named_priority( + origin: OriginFor, + id: TaskName, + priority: schedule::Priority, + ) -> DispatchResult { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_change_named_priority(origin.caller().clone(), id, priority) + } + } +} + +impl Pallet { + fn resolve_time(when: DispatchTime) -> Result { + let now = frame_system::Pallet::::block_number(); + + let when = match when { + DispatchTime::At(x) => x, + // The current block has already completed it's scheduled tasks, so + // Schedule the task at lest one block after this current block. + DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()), + }; + + if when <= now { + return Err(Error::::TargetBlockNumberInPast.into()); + } + + Ok(when) + } + + fn place_task( + when: T::BlockNumber, + what: ScheduledOf, + ) -> Result, (DispatchError, ScheduledOf)> { + let maybe_name = what.maybe_id; + let index = Self::push_to_agenda(when, what)?; + let address = (when, index); + if let Some(name) = maybe_name { + Lookup::::insert(name, address) + } + Self::deposit_event(Event::Scheduled { + when: address.0, + index: address.1, + }); + Ok(address) + } + + fn push_to_agenda( + when: T::BlockNumber, + what: ScheduledOf, + ) -> Result)> { + let mut agenda = Agenda::::get(when); + let index = if (agenda.len() as u32) < T::MaxScheduledPerBlock::get() { + // will always succeed due to the above check. + let _ = agenda.try_push(Some(what)); + agenda.len() as u32 - 1 + } else { + if let Some(hole_index) = agenda.iter().position(|i| i.is_none()) { + agenda[hole_index] = Some(what); + hole_index as u32 + } else { + return Err((>::AgendaIsExhausted.into(), what)); + } + }; + Agenda::::insert(when, agenda); + Ok(index) + } + + fn do_schedule( + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ScheduledCall, + ) -> Result, DispatchError> { + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + let task = Scheduled { + maybe_id: None, + priority, + call, + maybe_periodic, + origin, + _phantom: PhantomData, + }; + Self::place_task(when, task).map_err(|x| x.0) + } + + fn do_cancel( + origin: Option, + (when, index): TaskAddress, + ) -> Result<(), DispatchError> { + let scheduled = Agenda::::try_mutate(when, |agenda| { + agenda.get_mut(index as usize).map_or( + Ok(None), + |s| -> Result>, DispatchError> { + if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { + if matches!( + T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), + Some(Ordering::Less) | None + ) { + return Err(BadOrigin.into()); + } + }; + Ok(s.take()) + }, + ) + })?; + if let Some(s) = scheduled { + T::Preimages::drop(&s.call); + + if let Some(id) = s.maybe_id { + Lookup::::remove(id); + } + Self::deposit_event(Event::Canceled { when, index }); + Ok(()) + } else { + return Err(Error::::NotFound.into()); + } + } + + fn do_schedule_named( + id: TaskName, + when: DispatchTime, + maybe_periodic: Option>, + priority: schedule::Priority, + origin: T::PalletsOrigin, + call: ScheduledCall, + ) -> Result, DispatchError> { + // ensure id it is unique + if Lookup::::contains_key(&id) { + return Err(Error::::FailedToSchedule.into()); + } + + let when = Self::resolve_time(when)?; + + // sanitize maybe_periodic + let maybe_periodic = maybe_periodic + .filter(|p| p.1 > 1 && !p.0.is_zero()) + // Remove one from the number of repetitions since we will schedule one now. + .map(|(p, c)| (p, c - 1)); + + let task = Scheduled { + maybe_id: Some(id), + priority, + call, + maybe_periodic, + origin, + _phantom: Default::default(), + }; + Self::place_task(when, task).map_err(|x| x.0) + } + + fn do_cancel_named(origin: Option, id: TaskName) -> DispatchResult { + Lookup::::try_mutate_exists(id, |lookup| -> DispatchResult { + if let Some((when, index)) = lookup.take() { + let i = index as usize; + Agenda::::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 matches!( + T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), + Some(Ordering::Less) | None + ) { + return Err(BadOrigin.into()); + } + T::Preimages::drop(&s.call); + } + *s = None; + } + Ok(()) + })?; + Self::deposit_event(Event::Canceled { when, index }); + Ok(()) + } else { + return Err(Error::::NotFound.into()); + } + }) + } + + fn do_change_named_priority( + origin: T::PalletsOrigin, + id: TaskName, + priority: schedule::Priority, + ) -> DispatchResult { + match Lookup::::get(id) { + Some((when, index)) => { + let i = index as usize; + Agenda::::try_mutate(when, |agenda| { + if let Some(Some(s)) = agenda.get_mut(i) { + if matches!( + T::OriginPrivilegeCmp::cmp_privilege(&origin, &s.origin), + Some(Ordering::Less) | None + ) { + return Err(BadOrigin.into()); + } + + s.priority = priority; + Self::deposit_event(Event::PriorityChanged { + when, + index, + priority, + }); + } + Ok(()) + }) + } + None => Err(Error::::NotFound.into()), + } + } +} + +enum ServiceTaskError { + /// Could not be executed due to missing preimage. + Unavailable, + /// Could not be executed due to weight limitations. + Overweight, +} +use ServiceTaskError::*; + +/// A Scheduler-Runtime interface for finer payment handling. +pub trait DispatchCall { + /// Resolve the call dispatch, including any post-dispatch operations. + fn dispatch_call( + signer: Option, + function: ::RuntimeCall, + ) -> Result< + Result>, + TransactionValidityError, + >; +} + +impl Pallet { + /// Service up to `max` agendas queue starting from earliest incompletely executed agenda. + fn service_agendas(weight: &mut WeightCounter, now: T::BlockNumber, max: u32) { + if !weight.check_accrue(T::WeightInfo::service_agendas_base()) { + return; + } + + let mut incomplete_since = now + One::one(); + let mut when = IncompleteSince::::take().unwrap_or(now); + let mut executed = 0; + + let max_items = T::MaxScheduledPerBlock::get(); + let mut count_down = max; + let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items); + while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) { + if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) { + incomplete_since = incomplete_since.min(when); + } + when.saturating_inc(); + count_down.saturating_dec(); + } + incomplete_since = incomplete_since.min(when); + if incomplete_since <= now { + IncompleteSince::::put(incomplete_since); + } + } + + /// Returns `true` if the agenda was fully completed, `false` if it should be revisited at a + /// later block. + fn service_agenda( + weight: &mut WeightCounter, + executed: &mut u32, + now: T::BlockNumber, + when: T::BlockNumber, + max: u32, + ) -> bool { + let mut agenda = Agenda::::get(when); + let mut ordered = agenda + .iter() + .enumerate() + .filter_map(|(index, maybe_item)| { + maybe_item + .as_ref() + .map(|item| (index as u32, item.priority)) + }) + .collect::>(); + ordered.sort_by_key(|k| k.1); + let within_limit = + weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32)); + debug_assert!( + within_limit, + "weight limit should have been checked in advance" + ); + + // Items which we know can be executed and have postponed for execution in a later block. + let mut postponed = (ordered.len() as u32).saturating_sub(max); + // Items which we don't know can ever be executed. + let mut dropped = 0; + + for (agenda_index, _) in ordered.into_iter().take(max as usize) { + let task = match agenda[agenda_index as usize].take() { + None => continue, + Some(t) => t, + }; + let base_weight = T::WeightInfo::service_task( + task.call.lookup_len().map(|x| x as usize), + task.maybe_id.is_some(), + task.maybe_periodic.is_some(), + ); + if !weight.can_accrue(base_weight) { + postponed += 1; + break; + } + let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task); + agenda[agenda_index as usize] = match result { + Err((Unavailable, slot)) => { + dropped += 1; + slot + } + Err((Overweight, slot)) => { + postponed += 1; + slot + } + Ok(()) => { + *executed += 1; + None + } + }; + } + if postponed > 0 || dropped > 0 { + Agenda::::insert(when, agenda); + } else { + Agenda::::remove(when); + } + postponed == 0 + } + + /// Service (i.e. execute) the given task, being careful not to overflow the `weight` counter. + /// + /// This involves: + /// - removing and potentially replacing the `Lookup` entry for the task. + /// - realizing the task's call which can include a preimage lookup. + /// - Rescheduling the task for execution in a later agenda if periodic. + fn service_task( + weight: &mut WeightCounter, + now: T::BlockNumber, + when: T::BlockNumber, + agenda_index: u32, + is_first: bool, + mut task: ScheduledOf, + ) -> Result<(), (ServiceTaskError, Option>)> { + let (call, lookup_len) = match T::Preimages::peek(&task.call) { + Ok(c) => c, + Err(_) => { + if let Some(ref id) = task.maybe_id { + Lookup::::remove(id); + } + + return Err((Unavailable, Some(task))); + } + }; + + weight.check_accrue(T::WeightInfo::service_task( + lookup_len.map(|x| x as usize), + task.maybe_id.is_some(), + task.maybe_periodic.is_some(), + )); + + match Self::execute_dispatch(weight, task.origin.clone(), call) { + Err(Unavailable) => { + debug_assert!(false, "Checked to exist with `peek`"); + + if let Some(ref id) = task.maybe_id { + Lookup::::remove(id); + } + + Self::deposit_event(Event::CallUnavailable { + task: (when, agenda_index), + id: task.maybe_id, + }); + Err((Unavailable, Some(task))) + } + Err(Overweight) if is_first && !Self::is_runtime_upgraded() => { + T::Preimages::drop(&task.call); + + if let Some(ref id) = task.maybe_id { + Lookup::::remove(id); + } + + Self::deposit_event(Event::PermanentlyOverweight { + task: (when, agenda_index), + id: task.maybe_id, + }); + Err((Unavailable, Some(task))) + } + Err(Overweight) => { + // Preserve Lookup -- the task will be postponed. + Err((Overweight, Some(task))) + } + Ok(result) => { + Self::deposit_event(Event::Dispatched { + task: (when, agenda_index), + id: task.maybe_id, + result, + }); + + let is_canceled = task + .maybe_id + .as_ref() + .map(|id| !Lookup::::contains_key(id)) + .unwrap_or(false); + + match &task.maybe_periodic { + &Some((period, count)) if !is_canceled => { + if count > 1 { + task.maybe_periodic = Some((period, count - 1)); + } else { + task.maybe_periodic = None; + } + let wake = now.saturating_add(period); + match Self::place_task(wake, task) { + Ok(_) => {} + Err((_, task)) => { + // TODO: Leave task in storage somewhere for it to be rescheduled + // manually. + T::Preimages::drop(&task.call); + Self::deposit_event(Event::PeriodicFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + } + } + } + _ => { + if let Some(ref id) = task.maybe_id { + Lookup::::remove(id); + } + + T::Preimages::drop(&task.call) + } + } + Ok(()) + } + } + } + + fn is_runtime_upgraded() -> bool { + let last = system::LastRuntimeUpgrade::::get(); + let current = T::Version::get(); + + last.map(|v| v.was_upgraded(¤t)).unwrap_or(true) + } + + /// Make a dispatch to the given `call` from the given `origin`, ensuring that the `weight` + /// counter does not exceed its limit and that it is counted accurately (e.g. accounted using + /// post info if available). + /// + /// NOTE: Only the weight for this function will be counted (origin lookup, dispatch and the + /// call itself). + fn execute_dispatch( + weight: &mut WeightCounter, + origin: T::PalletsOrigin, + call: ::RuntimeCall, + ) -> Result { + let dispatch_origin: ::RuntimeOrigin = origin.into(); + let base_weight = match dispatch_origin.clone().as_signed() { + Some(_) => T::WeightInfo::execute_dispatch_signed(), + _ => T::WeightInfo::execute_dispatch_unsigned(), + }; + let call_weight = call.get_dispatch_info().weight; + // We only allow a scheduled call if it cannot push the weight past the limit. + let max_weight = base_weight.saturating_add(call_weight); + + if !weight.can_accrue(max_weight) { + return Err(Overweight); + } + + // let scheduled_origin = + // <::Origin as From>::from(origin.clone()); + let ensured_origin = T::ScheduleOrigin::ensure_origin(dispatch_origin.into()); + + let r = match ensured_origin { + Ok(ScheduledEnsureOriginSuccess::Root) => { + Ok(call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into())) + } + Ok(ScheduledEnsureOriginSuccess::Signed(sender)) => { + // Execute transaction via chain default pipeline + // That means dispatch will be processed like any user's extrinsic e.g. transaction fees will be taken + T::CallExecutor::dispatch_call(Some(sender), call.clone()) + } + Err(e) => Ok(Err(e.into())), + }; + + let (maybe_actual_call_weight, result) = match r { + Ok(result) => match result { + Ok(post_info) => (post_info.actual_weight, Ok(())), + Err(error_and_info) => ( + error_and_info.post_info.actual_weight, + Err(error_and_info.error), + ), + }, + Err(_) => { + log::error!( + target: "runtime::scheduler", + "Warning: Scheduler has failed to execute a post-dispatch transaction. \ + This block might have become invalid."); + (None, Err(DispatchError::CannotLookup)) + } + }; + let call_weight = maybe_actual_call_weight.unwrap_or(call_weight); + weight.check_accrue(base_weight); + weight.check_accrue(call_weight); + Ok(result) + } +} --- /dev/null +++ b/pallets/scheduler-v2/src/mock.rs @@ -0,0 +1,285 @@ +// 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 . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Scheduler test environment. + +use super::*; + +use crate as scheduler; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + weights::constants::RocksDbWeight, +}; +use frame_system::{EnsureRoot, RawOrigin}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +// Logger module to track execution. +#[frame_support::pallet] +pub mod logger { + use super::{OriginCaller, OriginTrait}; + use frame_support::{pallet_prelude::*, parameter_types}; + use frame_system::pallet_prelude::*; + + parameter_types! { + static Log: Vec<(OriginCaller, u32)> = Vec::new(); + } + pub fn log() -> Vec<(OriginCaller, u32)> { + Log::get().clone() + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Logged(u32, Weight), + } + + #[pallet::call] + impl Pallet + where + ::RuntimeOrigin: OriginTrait, + { + #[pallet::weight(*weight)] + pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } + + #[pallet::weight(*weight)] + pub fn log_without_filter(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } + } +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Logger: logger::{Pallet, Call, Event}, + Scheduler: scheduler::{Pallet, Call, Storage, Event}, + } +); + +// Scheduler must dispatch with root and no filter, this tests base filter is indeed not used. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + !matches!(call, RuntimeCall::Logger(LoggerCall::log { .. })) + } +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_ref_time(2_000_000_000_000) + // .set_proof_size(u64::MAX), + ); +} +impl system::Config for Test { + type BaseCallFilter = BaseFilter; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + 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 RuntimeEvent = RuntimeEvent; +} +ord_parameter_types! { + pub const One: u64 = 1; +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn service_agendas_base() -> Weight { + Weight::from_ref_time(0b0000_0001) + } + fn service_agenda_base(i: u32) -> Weight { + Weight::from_ref_time((i << 8) as u64 + 0b0000_0010) + } + fn service_task_base() -> Weight { + Weight::from_ref_time(0b0000_0100) + } + fn service_task_periodic() -> Weight { + Weight::from_ref_time(0b0000_1100) + } + fn service_task_named() -> Weight { + Weight::from_ref_time(0b0001_0100) + } + fn service_task_fetched(s: u32) -> Weight { + Weight::from_ref_time((s << 8) as u64 + 0b0010_0100) + } + fn execute_dispatch_signed() -> Weight { + Weight::from_ref_time(0b0100_0000) + } + fn execute_dispatch_unsigned() -> Weight { + Weight::from_ref_time(0b1000_0000) + } + fn schedule(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn cancel(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn schedule_named(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn cancel_named(_s: u32) -> Weight { + Weight::from_ref_time(50) + } + fn change_named_priority(_s: u32) -> Weight { + Weight::from_ref_time(50) + } +} +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + BlockWeights::get().max_block; +} + +pub struct EnsureSignedOneOrRoot; +impl, O>> + From>> EnsureOrigin + for EnsureSignedOneOrRoot +{ + type Success = ScheduledEnsureOriginSuccess; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Root => Ok(ScheduledEnsureOriginSuccess::Root), + RawOrigin::Signed(1) => Ok(ScheduledEnsureOriginSuccess::Signed(1)), + r => Err(O::from(r)), + }) + } +} + +pub struct Executor; +impl DispatchCall for Executor { + fn dispatch_call( + signer: Option, + function: RuntimeCall, + ) -> Result< + Result>, + TransactionValidityError, + > { + let origin = match signer { + Some(who) => RuntimeOrigin::signed(who), + None => RuntimeOrigin::none(), + }; + Ok(function.dispatch(origin)) + } +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureSignedOneOrRoot; + type MaxScheduledPerBlock = ConstU32<10>; + type WeightInfo = TestWeightInfo; + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = (); + type PrioritySetOrigin = EnsureRoot; + type CallExecutor = Executor; +} + +pub type LoggerCall = logger::Call; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + t.into() +} + +pub fn run_to_block(n: u64) { + while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Scheduler::on_initialize(System::block_number()); + } +} + +pub fn root() -> OriginCaller { + system::RawOrigin::Root.into() +} --- /dev/null +++ b/pallets/scheduler-v2/src/tests.rs @@ -0,0 +1,874 @@ +// 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 . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Scheduler tests. + +use super::*; +use crate::mock::{ + logger, new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, +}; +use frame_support::{ + assert_noop, assert_ok, + traits::{Contains, OnInitialize}, +}; + +#[test] +fn basic_scheduling_works() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + assert!(!::BaseCallFilter::contains( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn schedule_after_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + assert!(!::BaseCallFilter::contains( + &call + )); + // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(3), + None, + 127, + root(), + >::new(call).unwrap(), + )); + run_to_block(5); + assert!(logger::log().is_empty()); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn schedule_after_zero_works() { + new_test_ext().execute_with(|| { + run_to_block(2); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + assert!(!::BaseCallFilter::contains( + &call + )); + assert_ok!(Scheduler::do_schedule( + DispatchTime::After(0), + None, + 127, + root(), + >::new(call).unwrap(), + )); + // Will trigger on the next block. + run_to_block(3); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + >::new(RuntimeCall::Logger(logger::Call::log { + i: 42, + weight: Weight::from_ref_time(10) + })) + .unwrap() + )); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(7); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(10); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + run_to_block(100); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + }); +} + +#[test] +fn cancel_named_scheduling_works_with_normal_cancel() { + new_test_ext().execute_with(|| { + // at #4. + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + >::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + let i = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + assert_ok!(Scheduler::do_cancel(None, i)); + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn cancel_named_periodic_scheduling_works() { + new_test_ext().execute_with(|| { + // at #4, every 3 blocks, 3 times. + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + Some((3, 3)), + 127, + root(), + >::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + // same id results in error. + assert!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + >::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10) + })) + .unwrap(), + ) + .is_err()); + // different id is ok. + Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(8), + None, + 127, + root(), + >::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })) + .unwrap(), + ) + .unwrap(); + run_to_block(3); + assert!(logger::log().is_empty()); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(6); + assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); +} + +#[test] +fn scheduler_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: max_weight / 3 * 2, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: max_weight / 3 * 2, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + )); + // 69 and 42 do not fit together + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(5); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); + }); +} + +/// Permanently overweight calls are not deleted but also not executed. +#[test] +fn scheduler_does_not_delete_permanently_overweight_call() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: max_weight, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + )); + // Never executes. + run_to_block(100); + assert_eq!(logger::log(), vec![]); + + // Assert the `PermanentlyOverweight` event. + assert_eq!( + System::events().last().unwrap().event, + crate::Event::PermanentlyOverweight { + task: (4, 0), + id: None + } + .into(), + ); + // The call is still in the agenda. + assert!(Agenda::::get(4)[0].is_some()); + }); +} + +#[test] +fn scheduler_handles_periodic_failure() { + let max_weight: Weight = ::MaximumWeight::get(); + let max_per_block = ::MaxScheduledPerBlock::get(); + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: (max_weight / 3) * 2, + }); + let call = >::new(call).unwrap(); + + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((4, u32::MAX)), + 127, + root(), + call.clone(), + )); + // Executes 5 times till block 20. + run_to_block(20); + assert_eq!(logger::log().len(), 5); + + // Block 28 will already be full. + for _ in 0..max_per_block { + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(28), + None, + 120, + root(), + call.clone(), + )); + } + + // Going to block 24 will emit a `PeriodicFailed` event. + run_to_block(24); + assert_eq!(logger::log().len(), 6); + + assert_eq!( + System::events().last().unwrap().event, + crate::Event::PeriodicFailed { + task: (24, 0), + id: None + } + .into(), + ); + }); +} + +#[test] +fn scheduler_respects_priority_ordering() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: max_weight / 3, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 1, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: max_weight / 3, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 0, + root(), + >::new(call).unwrap(), + )); + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); + }); +} + +#[test] +fn scheduler_respects_priority_ordering_with_soft_deadlines() { + new_test_ext().execute_with(|| { + let max_weight: Weight = ::MaximumWeight::get(); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: max_weight / 5 * 2, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 255, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: max_weight / 5 * 2, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 2600, + weight: max_weight / 5 * 4, + }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 126, + root(), + >::new(call).unwrap(), + )); + + // 2600 does not fit with 69 or 42, but has higher priority, so will go through + run_to_block(4); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + // 69 and 42 fit together + run_to_block(5); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + }); +} + +#[test] +fn on_initialize_weight_is_correct() { + new_test_ext().execute_with(|| { + let call_weight = Weight::from_ref_time(25); + + // Named + let call = RuntimeCall::Logger(LoggerCall::log { + i: 3, + weight: call_weight + Weight::from_ref_time(1), + }); + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(3), + None, + 255, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: call_weight + Weight::from_ref_time(2), + }); + // Anon Periodic + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(2), + Some((1000, 3)), + 128, + root(), + >::new(call).unwrap(), + )); + let call = RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: call_weight + Weight::from_ref_time(3), + }); + // Anon + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(2), + None, + 127, + root(), + >::new(call).unwrap(), + )); + // Named Periodic + let call = RuntimeCall::Logger(LoggerCall::log { + i: 2600, + weight: call_weight + Weight::from_ref_time(4), + }); + assert_ok!(Scheduler::do_schedule_named( + [2u8; 32], + DispatchTime::At(1), + Some((1000, 3)), + 126, + root(), + >::new(call).unwrap(), + )); + + // Will include the named periodic only + assert_eq!( + Scheduler::on_initialize(1), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(1) + + ::service_task(None, true, true) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(4) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!(logger::log(), vec![(root(), 2600u32)]); + + // Will include anon and anon periodic + assert_eq!( + Scheduler::on_initialize(2), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(2) + + ::service_task(None, false, true) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(3) + + ::service_task(None, false, false) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(2) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!( + logger::log(), + vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)] + ); + + // Will include named only + assert_eq!( + Scheduler::on_initialize(3), + TestWeightInfo::service_agendas_base() + + TestWeightInfo::service_agenda_base(1) + + ::service_task(None, true, false) + + TestWeightInfo::execute_dispatch_unsigned() + + call_weight + Weight::from_ref_time(1) + ); + assert_eq!(IncompleteSince::::get(), None); + assert_eq!( + logger::log(), + vec![ + (root(), 2600u32), + (root(), 69u32), + (root(), 42u32), + (root(), 3u32) + ] + ); + + // Will contain none + let actual_weight = Scheduler::on_initialize(4); + assert_eq!( + actual_weight, + TestWeightInfo::service_agendas_base() + TestWeightInfo::service_agenda_base(0) + ); + }); +} + +#[test] +fn root_calls_works() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!(Scheduler::schedule_named( + RuntimeOrigin::root(), + [1u8; 32], + 4, + None, + Some(127), + call, + )); + assert_ok!(Scheduler::schedule( + RuntimeOrigin::root(), + 4, + None, + Some(127), + call2 + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32])); + assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn fails_to_schedule_task_in_the_past() { + new_test_ext().execute_with(|| { + run_to_block(3); + + let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + let call3 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + + assert_noop!( + Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 2, None, Some(127), call1), + Error::::TargetBlockNumberInPast, + ); + + assert_noop!( + Scheduler::schedule(RuntimeOrigin::root(), 2, None, Some(127), call2), + Error::::TargetBlockNumberInPast, + ); + + assert_noop!( + Scheduler::schedule(RuntimeOrigin::root(), 3, None, Some(127), call3), + Error::::TargetBlockNumberInPast, + ); + }); +} + +#[test] +fn should_use_origin() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + [1u8; 32], + 4, + None, + None, + call, + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + None, + call2, + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_ok!(Scheduler::cancel_named( + system::RawOrigin::Signed(1).into(), + [1u8; 32] + )); + assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); + // Scheduled calls are made NONE, so should not effect state + run_to_block(100); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn should_check_origin() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_noop!( + Scheduler::schedule_named( + system::RawOrigin::Signed(2).into(), + [1u8; 32], + 4, + None, + None, + call + ), + BadOrigin + ); + assert_noop!( + Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, None, call2), + BadOrigin + ); + }); +} + +#[test] +fn should_check_origin_for_cancel() { + new_test_ext().execute_with(|| { + let call = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { + i: 69, + weight: Weight::from_ref_time(10), + })); + let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { + i: 42, + weight: Weight::from_ref_time(10), + })); + assert_ok!(Scheduler::schedule_named( + system::RawOrigin::Signed(1).into(), + [1u8; 32], + 4, + None, + None, + call, + )); + assert_ok!(Scheduler::schedule( + system::RawOrigin::Signed(1).into(), + 4, + None, + None, + call2, + )); + run_to_block(3); + // Scheduled calls are in the agenda. + assert_eq!(Agenda::::get(4).len(), 2); + assert!(logger::log().is_empty()); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), [1u8; 32]), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), + BadOrigin + ); + assert_noop!( + Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), + BadOrigin + ); + assert_noop!( + Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), + BadOrigin + ); + run_to_block(5); + assert_eq!( + logger::log(), + vec![ + (system::RawOrigin::Signed(1).into(), 69u32), + (system::RawOrigin::Signed(1).into(), 42u32) + ] + ); + }); +} + +/// Cancelling a call and then scheduling a second call for the same +/// block results in different addresses. +#[test] +fn schedule_does_not_resuse_addr() { + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + + // Schedule both calls. + let addr_1 = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call.clone()).unwrap(), + ) + .unwrap(); + // Cancel the call. + assert_ok!(Scheduler::do_cancel(None, addr_1)); + let addr_2 = Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + >::new(call).unwrap(), + ) + .unwrap(); + + // Should not re-use the address. + assert!(addr_1 != addr_2); + }); +} + +#[test] +fn schedule_agenda_overflows() { + let max: u32 = ::MaxScheduledPerBlock::get(); + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + let call = >::new(call).unwrap(); + + // Schedule the maximal number allowed per block. + for _ in 0..max { + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call.clone()).unwrap(); + } + + // One more time and it errors. + assert_noop!( + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call,), + >::AgendaIsExhausted, + ); + + run_to_block(4); + // All scheduled calls are executed. + assert_eq!(logger::log().len() as u32, max); + }); +} + +/// Cancelling and scheduling does not overflow the agenda but fills holes. +#[test] +fn cancel_and_schedule_fills_holes() { + let max: u32 = ::MaxScheduledPerBlock::get(); + assert!( + max > 3, + "This test only makes sense for MaxScheduledPerBlock > 3" + ); + + new_test_ext().execute_with(|| { + let call = RuntimeCall::Logger(LoggerCall::log { + i: 42, + weight: Weight::from_ref_time(10), + }); + let call = >::new(call).unwrap(); + let mut addrs = Vec::<_>::default(); + + // Schedule the maximal number allowed per block. + for _ in 0..max { + addrs.push( + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call.clone()) + .unwrap(), + ); + } + // Cancel three of them. + for addr in addrs.into_iter().take(3) { + Scheduler::do_cancel(None, addr).unwrap(); + } + // Schedule three new ones. + for i in 0..3 { + let (_block, index) = + Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call.clone()) + .unwrap(); + assert_eq!(i, index); + } + + run_to_block(4); + // Maximum number of calls are executed. + assert_eq!(logger::log().len() as u32, max); + }); +} --- /dev/null +++ b/pallets/scheduler-v2/src/weights.rs @@ -0,0 +1,270 @@ +// 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 . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_scheduler +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-10-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /home/benchbot/cargo_target_dir/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --pallet=pallet_scheduler +// --chain=dev +// --output=./frame/scheduler/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_scheduler. +pub trait WeightInfo { + fn service_agendas_base() -> Weight; + fn service_agenda_base(s: u32, ) -> Weight; + fn service_task_base() -> Weight; + fn service_task_fetched(s: u32, ) -> Weight; + fn service_task_named() -> Weight; + fn service_task_periodic() -> Weight; + fn execute_dispatch_signed() -> Weight; + fn execute_dispatch_unsigned() -> Weight; + fn schedule(s: u32, ) -> Weight; + fn cancel(s: u32, ) -> Weight; + fn schedule_named(s: u32, ) -> Weight; + fn cancel_named(s: u32, ) -> Weight; + fn change_named_priority(s: u32, ) -> Weight; +} + +/// Weights for pallet_scheduler using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Scheduler IncompleteSince (r:1 w:1) + fn service_agendas_base() -> Weight { + Weight::from_ref_time(4_992_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 512]`. + fn service_agenda_base(s: u32, ) -> Weight { + Weight::from_ref_time(4_320_000 as u64) + // Standard Error: 619 + .saturating_add(Weight::from_ref_time(336_713 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn service_task_base() -> Weight { + Weight::from_ref_time(10_864_000 as u64) + } + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `s` is `[128, 4194304]`. + fn service_task_fetched(s: u32, ) -> Weight { + Weight::from_ref_time(24_586_000 as u64) + // Standard Error: 1 + .saturating_add(Weight::from_ref_time(1_138 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:0 w:1) + fn service_task_named() -> Weight { + Weight::from_ref_time(13_127_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn service_task_periodic() -> Weight { + Weight::from_ref_time(11_053_000 as u64) + } + fn execute_dispatch_signed() -> Weight { + Weight::from_ref_time(4_158_000 as u64) + } + fn execute_dispatch_unsigned() -> Weight { + Weight::from_ref_time(4_104_000 as u64) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. + fn schedule(s: u32, ) -> Weight { + Weight::from_ref_time(20_074_000 as u64) + // Standard Error: 765 + .saturating_add(Weight::from_ref_time(343_285 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Scheduler Lookup (r:0 w:1) + /// The range of component `s` is `[1, 512]`. + fn cancel(s: u32, ) -> Weight { + Weight::from_ref_time(21_509_000 as u64) + // Standard Error: 708 + .saturating_add(Weight::from_ref_time(323_013 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. + fn schedule_named(s: u32, ) -> Weight { + Weight::from_ref_time(22_427_000 as u64) + // Standard Error: 850 + .saturating_add(Weight::from_ref_time(357_265 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[1, 512]`. + fn cancel_named(s: u32, ) -> Weight { + Weight::from_ref_time(22_875_000 as u64) + // Standard Error: 693 + .saturating_add(Weight::from_ref_time(336_643 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn change_named_priority(s: u32, ) -> Weight { + Weight::from_ref_time(8_642_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(431_000).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Scheduler IncompleteSince (r:1 w:1) + fn service_agendas_base() -> Weight { + Weight::from_ref_time(4_992_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 512]`. + fn service_agenda_base(s: u32, ) -> Weight { + Weight::from_ref_time(4_320_000 as u64) + // Standard Error: 619 + .saturating_add(Weight::from_ref_time(336_713 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn service_task_base() -> Weight { + Weight::from_ref_time(10_864_000 as u64) + } + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + /// The range of component `s` is `[128, 4194304]`. + fn service_task_fetched(s: u32, ) -> Weight { + Weight::from_ref_time(24_586_000 as u64) + // Standard Error: 1 + .saturating_add(Weight::from_ref_time(1_138 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:0 w:1) + fn service_task_named() -> Weight { + Weight::from_ref_time(13_127_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn service_task_periodic() -> Weight { + Weight::from_ref_time(11_053_000 as u64) + } + fn execute_dispatch_signed() -> Weight { + Weight::from_ref_time(4_158_000 as u64) + } + fn execute_dispatch_unsigned() -> Weight { + Weight::from_ref_time(4_104_000 as u64) + } + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. + fn schedule(s: u32, ) -> Weight { + Weight::from_ref_time(20_074_000 as u64) + // Standard Error: 765 + .saturating_add(Weight::from_ref_time(343_285 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Scheduler Agenda (r:1 w:1) + // Storage: Scheduler Lookup (r:0 w:1) + /// The range of component `s` is `[1, 512]`. + fn cancel(s: u32, ) -> Weight { + Weight::from_ref_time(21_509_000 as u64) + // Standard Error: 708 + .saturating_add(Weight::from_ref_time(323_013 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[0, 511]`. + fn schedule_named(s: u32, ) -> Weight { + Weight::from_ref_time(22_427_000 as u64) + // Standard Error: 850 + .saturating_add(Weight::from_ref_time(357_265 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + /// The range of component `s` is `[1, 512]`. + fn cancel_named(s: u32, ) -> Weight { + Weight::from_ref_time(22_875_000 as u64) + // Standard Error: 693 + .saturating_add(Weight::from_ref_time(336_643 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn change_named_priority(s: u32, ) -> Weight { + Weight::from_ref_time(8_642_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(431_000).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} --- a/pallets/scheduler/src/benchmarking.rs +++ b/pallets/scheduler/src/benchmarking.rs @@ -50,6 +50,13 @@ const BLOCK_NUMBER: u32 = 2; +fn make_scheduled_id(src: u32) -> ScheduledId { + let slice_id: [u8; 4] = src.encode().try_into().unwrap(); + let mut id: [u8; 16] = [0; 16]; + id[..4].clone_from_slice(&slice_id); + id +} + /// Add `n` named items to the schedule. /// /// For `resolved`: @@ -79,10 +86,7 @@ false => None, }; - let slice_id: [u8; 4] = i.encode().try_into().unwrap(); - let mut id: [u8; 16] = [0; 16]; - id[..4].clone_from_slice(&slice_id); - + let id = make_scheduled_id(i); let origin = frame_system::RawOrigin::Signed(caller.clone()).into(); Scheduler::::do_schedule_named(id, t, period, 0, origin, call_or_hash)?; } @@ -202,7 +206,7 @@ id[..4].clone_from_slice(&slice_id); let when = BLOCK_NUMBER.into(); let periodic = Some((T::BlockNumber::one(), 100)); - let priority = 0; + let priority = None; // Essentially a no-op call. let inner_call = frame_system::Call::set_storage { items: vec![] }.into(); let call = Box::new(CallOrHashOf::::Value(inner_call)); @@ -220,7 +224,8 @@ let origin: RawOrigin = frame_system::RawOrigin::Signed(caller.clone()); let s in 1 .. T::MaxScheduledPerBlock::get(); let when = BLOCK_NUMBER.into(); - let id = 0.encode().try_into().unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]); + let idx = s - 1; + let id = make_scheduled_id(idx); fill_schedule::(when, s, true, Some(false))?; }: _(origin, id) verify { @@ -230,10 +235,26 @@ ); // Removed schedule is NONE ensure!( - Agenda::::get(when)[0].is_none(), + Agenda::::get(when)[idx as usize].is_none(), "didn't remove from schedule" ); } + change_named_priority { + let origin: RawOrigin = frame_system::RawOrigin::Root; + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + let idx = s - 1; + let id = make_scheduled_id(idx); + let priority = 42; + fill_schedule::(when, s, true, Some(false))?; + }: _(origin, id, priority) + verify { + ensure!( + Agenda::::get(when)[idx as usize].clone().unwrap().priority == priority, + "didn't change the priority" + ); + } + impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); } --- a/pallets/scheduler/src/lib.rs +++ b/pallets/scheduler/src/lib.rs @@ -86,7 +86,10 @@ use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; use frame_support::{ - dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter, GetDispatchInfo}, + dispatch::{ + DispatchError, DispatchResult, Dispatchable, UnfilteredDispatchable, Parameter, + GetDispatchInfo, + }, traits::{ schedule::{self, DispatchTime, MaybeHashed}, NamedReservableCurrency, EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp, @@ -97,13 +100,11 @@ pub use weights::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, u32); pub const MAX_TASK_ID_LENGTH_IN_BYTES: u8 = 16; -type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize]; +pub type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize]; pub type CallOrHashOf = MaybeHashed<::RuntimeCall, ::Hash>; @@ -137,6 +138,11 @@ pub type Scheduled = ScheduledV3; +pub enum ScheduledEnsureOriginSuccess { + Root, + Signed(AccountId), +} + #[cfg(feature = "runtime-benchmarks")] mod preimage_provider { use frame_support::traits::PreimageRecipient; @@ -193,7 +199,10 @@ use frame_support::{ dispatch::PostDispatchInfo, pallet_prelude::*, - traits::{schedule::LookupError, PreimageProvider}, + traits::{ + schedule::{LookupError, LOWEST_PRIORITY}, + PreimageProvider, + }, }; use frame_system::pallet_prelude::*; @@ -227,7 +236,8 @@ + Dispatchable< RuntimeOrigin = ::RuntimeOrigin, PostInfo = PostDispatchInfo, - > + GetDispatchInfo + > + UnfilteredDispatchable::RuntimeOrigin> + + GetDispatchInfo + From>; /// The maximum weight that may be scheduled per block for any dispatchables of less @@ -236,7 +246,13 @@ type MaximumWeight: Get; /// Required origin to schedule or cancel calls. - type ScheduleOrigin: EnsureOrigin<::RuntimeOrigin>; + type ScheduleOrigin: EnsureOrigin< + ::RuntimeOrigin, + Success = ScheduledEnsureOriginSuccess, + >; + + /// Required origin to set/change calls' priority. + type PrioritySetOrigin: EnsureOrigin<::RuntimeOrigin>; /// Compare the privileges of origins. /// @@ -287,7 +303,7 @@ /// Resolve the call dispatch, including any post-dispatch operations. fn dispatch_call( - signer: T::AccountId, + signer: Option, function: ::RuntimeCall, ) -> Result< Result>, @@ -319,6 +335,12 @@ Scheduled { when: T::BlockNumber, index: u32 }, /// Canceled some task. Canceled { when: T::BlockNumber, index: u32 }, + /// Scheduled task's priority has changed + PriorityChanged { + when: T::BlockNumber, + index: u32, + priority: schedule::Priority, + }, /// Dispatched some task. Dispatched { task: TaskAddress, @@ -371,12 +393,7 @@ 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::::remove(id); - true - } else { - false - }; + let named = s.maybe_id.is_some(); let (call, maybe_completed) = s.call.resolved::(); s.call = call; @@ -399,6 +416,8 @@ Lookup::::insert(id, (until, index as u32)); } Agenda::::append(until, Some(s)); + } else if let Some(ref id) = s.maybe_id { + Lookup::::remove(id); } continue; } @@ -439,26 +458,23 @@ continue; } - let sender = ensure_signed( + let scheduled_origin = <::RuntimeOrigin as From>::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 ensured_origin = T::ScheduleOrigin::ensure_origin(scheduled_origin.into()); - // Execute transaction via chain default pipeline - // That means dispatch will be processed like any user's extrinsic e.g. transaction fees will be taken - let r = T::CallExecutor::dispatch_call(sender, call.clone()); + let r = match ensured_origin { + Ok(ScheduledEnsureOriginSuccess::Root) => { + Ok(call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into())) + } + Ok(ScheduledEnsureOriginSuccess::Signed(sender)) => { + // Execute transaction via chain default pipeline + // That means dispatch will be processed like any user's extrinsic e.g. transaction fees will be taken + T::CallExecutor::dispatch_call(Some(sender), call.clone()) + } + Err(e) => Ok(Err(e.into())), + }; let mut actual_call_weight: Weight = item_weight; let result: Result<_, DispatchError> = match r { @@ -494,16 +510,28 @@ s.maybe_periodic = None; } let wake = now + period; + let is_canceled; + // If scheduled is named, place its information in `Lookup` if let Some(ref id) = s.maybe_id { + is_canceled = Lookup::::get(id).is_none(); let wake_index = Agenda::::decode_len(wake).unwrap_or(0); - Lookup::::insert(id, (wake, wake_index as u32)); + + if !is_canceled { + Lookup::::insert(id, (wake, wake_index as u32)); + } + } else { + is_canceled = false; + } + + if !is_canceled { + Agenda::::append(wake, Some(s)); } - Agenda::::append(wake, Some(s)); + } else if let Some(ref id) = s.maybe_id { + Lookup::::remove(id); } } - // Total weight should be 0, because the transaction is already paid for - Weight::zero() + total_weight } } @@ -516,16 +544,21 @@ id: ScheduledId, when: T::BlockNumber, maybe_periodic: Option>, - priority: schedule::Priority, + priority: Option, call: Box>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule_named( id, DispatchTime::At(when), maybe_periodic, - priority, + priority.unwrap_or(LOWEST_PRIORITY), origin.caller().clone(), *call, )?; @@ -552,21 +585,37 @@ id: ScheduledId, after: T::BlockNumber, maybe_periodic: Option>, - priority: schedule::Priority, + priority: Option, call: Box>, ) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; + + if priority.is_some() { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + } + let origin = ::RuntimeOrigin::from(origin); Self::do_schedule_named( id, DispatchTime::After(after), maybe_periodic, - priority, + priority.unwrap_or(LOWEST_PRIORITY), origin.caller().clone(), *call, )?; Ok(()) } + + #[pallet::weight(::WeightInfo::change_named_priority(T::MaxScheduledPerBlock::get()))] + pub fn change_named_priority( + origin: OriginFor, + id: ScheduledId, + priority: schedule::Priority, + ) -> DispatchResult { + T::PrioritySetOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_change_named_priority(origin.caller().clone(), id, priority) + } } } @@ -719,4 +768,35 @@ } }) } + + fn do_change_named_priority( + origin: T::PalletsOrigin, + id: ScheduledId, + priority: schedule::Priority, + ) -> DispatchResult { + match Lookup::::get(id) { + Some((when, index)) => { + let i = index as usize; + Agenda::::try_mutate(when, |agenda| { + if let Some(Some(s)) = agenda.get_mut(i) { + if matches!( + T::OriginPrivilegeCmp::cmp_privilege(&origin, &s.origin), + Some(Ordering::Less) | None + ) { + return Err(BadOrigin.into()); + } + + s.priority = priority; + Self::deposit_event(Event::PriorityChanged { + when, + index, + priority, + }); + } + Ok(()) + }) + } + None => Err(Error::::NotFound.into()), + } + } } --- a/pallets/scheduler/src/weights.rs +++ b/pallets/scheduler/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_unique_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-08-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-09-14, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -26,6 +26,7 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] #![allow(clippy::unnecessary_cast)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; @@ -44,6 +45,7 @@ fn on_initialize_resolved(s: u32, ) -> Weight; fn schedule_named(s: u32, ) -> Weight; fn cancel_named(s: u32, ) -> Weight; + fn change_named_priority(s: u32, ) -> Weight; } /// Weights for pallet_unique_scheduler using the Substrate node and recommended hardware. @@ -53,7 +55,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { Weight::from_ref_time(26_641_000) // Standard Error: 9_000 @@ -67,6 +71,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { Weight::from_ref_time(23_941_000) @@ -80,7 +86,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic(s: u32, ) -> Weight { Weight::from_ref_time(24_858_000) // Standard Error: 7_000 @@ -94,7 +102,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { Weight::from_ref_time(25_515_000) // Standard Error: 14_000 @@ -118,6 +128,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { Weight::from_ref_time(25_552_000) @@ -141,6 +153,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize(s: u32, ) -> Weight { Weight::from_ref_time(24_482_000) @@ -154,6 +168,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { Weight::from_ref_time(25_187_000) @@ -181,6 +197,15 @@ .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn change_named_priority(s: u32, ) -> Weight { + Weight::from_ref_time(8_642_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(431_000).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } } // For backwards compatibility and tests @@ -189,7 +214,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { Weight::from_ref_time(26_641_000) // Standard Error: 9_000 @@ -203,6 +230,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { Weight::from_ref_time(23_941_000) @@ -216,7 +245,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic(s: u32, ) -> Weight { Weight::from_ref_time(24_858_000) // Standard Error: 7_000 @@ -230,7 +261,9 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: Scheduler Lookup (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { Weight::from_ref_time(25_515_000) // Standard Error: 14_000 @@ -254,6 +287,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { Weight::from_ref_time(25_552_000) @@ -277,6 +312,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize(s: u32, ) -> Weight { Weight::from_ref_time(24_482_000) @@ -290,6 +327,8 @@ // Storage: System Account (r:1 w:1) // Storage: System AllExtrinsicsLen (r:1 w:1) // Storage: System BlockWeight (r:1 w:1) + // Storage: Configuration WeightToFeeCoefficientOverride (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { Weight::from_ref_time(25_187_000) @@ -317,4 +356,14 @@ .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } + + // Storage: Scheduler Lookup (r:1 w:1) + // Storage: Scheduler Agenda (r:1 w:1) + fn change_named_priority(s: u32, ) -> Weight { + Weight::from_ref_time(8_642_000) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(431_000).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } } --- a/runtime/common/config/mod.rs +++ b/runtime/common/config/mod.rs @@ -21,3 +21,6 @@ pub mod sponsoring; pub mod substrate; pub mod xcm; + +#[cfg(feature = "pallet-test-utils")] +pub mod test_pallets; --- a/runtime/common/config/pallets/scheduler.rs +++ b/runtime/common/config/pallets/scheduler.rs @@ -14,14 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -use frame_support::{traits::PrivilegeCmp, weights::Weight, parameter_types}; -use frame_system::EnsureSigned; +use frame_support::{ + traits::{PrivilegeCmp, EnsureOrigin}, + weights::Weight, + parameter_types, +}; +use frame_system::{EnsureRoot, RawOrigin}; use sp_runtime::Perbill; -use sp_std::cmp::Ordering; +use core::cmp::Ordering; +use codec::Decode; use crate::{ runtime_common::{scheduler::SchedulerPaymentExecutor, config::substrate::RuntimeBlockWeights}, - Runtime, Call, Event, Origin, OriginCaller, Balances, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, OriginCaller, Balances, }; +use pallet_unique_scheduler_v2::ScheduledEnsureOriginSuccess; use up_common::types::AccountId; parameter_types! { @@ -33,27 +39,65 @@ pub const Preimage: Option = Some(10); } -/// Used the compare the privilege of an origin inside the scheduler. -pub struct OriginPrivilegeCmp; +pub struct EnsureSignedOrRoot(sp_std::marker::PhantomData); +impl, O>> + From>, AccountId: Decode> + EnsureOrigin for EnsureSignedOrRoot +{ + type Success = ScheduledEnsureOriginSuccess; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Root => Ok(ScheduledEnsureOriginSuccess::Root), + RawOrigin::Signed(who) => Ok(ScheduledEnsureOriginSuccess::Signed(who)), + r => Err(O::from(r)), + }) + } +} + +pub struct EqualOrRootOnly; +impl PrivilegeCmp for EqualOrRootOnly { + fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { + use RawOrigin::*; + + let left = left.clone().try_into().ok()?; + let right = right.clone().try_into().ok()?; -impl PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - Some(Ordering::Equal) + match (left, right) { + (Root, Root) => Some(Ordering::Equal), + (Root, _) => Some(Ordering::Greater), + (_, Root) => Some(Ordering::Less), + lr @ _ => (lr.0 == lr.1).then(|| Ordering::Equal), + } } } -impl pallet_unique_scheduler::Config for Runtime { +// impl pallet_unique_scheduler::Config for Runtime { +// type RuntimeEvent = RuntimeEvent; +// type RuntimeOrigin = RuntimeOrigin; +// type Currency = Balances; +// type PalletsOrigin = OriginCaller; +// type RuntimeCall = RuntimeCall; +// type MaximumWeight = MaximumSchedulerWeight; +// type ScheduleOrigin = EnsureSignedOrRoot; +// type PrioritySetOrigin = EnsureRoot; +// type MaxScheduledPerBlock = MaxScheduledPerBlock; +// type WeightInfo = (); +// type CallExecutor = SchedulerPaymentExecutor; +// type OriginPrivilegeCmp = EqualOrRootOnly; +// type PreimageProvider = (); +// type NoPreimagePostponement = NoPreimagePostponement; +// } + +impl pallet_unique_scheduler_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; - type Currency = Balances; type PalletsOrigin = OriginCaller; type RuntimeCall = RuntimeCall; type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureSigned; + type ScheduleOrigin = EnsureSignedOrRoot; + type OriginPrivilegeCmp = EqualOrRootOnly; type MaxScheduledPerBlock = MaxScheduledPerBlock; type WeightInfo = (); + type Preimages = (); type CallExecutor = SchedulerPaymentExecutor; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type PreimageProvider = (); - type NoPreimagePostponement = NoPreimagePostponement; + type PrioritySetOrigin = EnsureRoot; } --- /dev/null +++ b/runtime/common/config/test_pallets.rs @@ -0,0 +1,21 @@ +// 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 . + +use crate::{Runtime, RuntimeEvent}; + +impl pallet_test_utils::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} --- a/runtime/common/construct_runtime/mod.rs +++ b/runtime/common/construct_runtime/mod.rs @@ -93,6 +93,12 @@ EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage, Event} = 151, EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, + + #[runtimes(opal)] + Scheduler: pallet_unique_scheduler_v2::{Pallet, Call, Storage, Event} = 154, + + #[runtimes(opal)] + TestUtils: pallet_test_utils = 255, } } } --- a/runtime/common/scheduler.rs +++ b/runtime/common/scheduler.rs @@ -16,7 +16,7 @@ use frame_support::{ traits::NamedReservableCurrency, - weights::{GetDispatchInfo, PostDispatchInfo, DispatchInfo}, + dispatch::{GetDispatchInfo, PostDispatchInfo, DispatchInfo}, }; use sp_runtime::{ traits::{Dispatchable, Applyable, Member}, @@ -24,10 +24,15 @@ transaction_validity::TransactionValidityError, DispatchErrorWithPostInfo, DispatchError, }; -use crate::{Runtime, Call, Origin, Balances, ChargeTransactionPayment}; +use codec::Encode; +use crate::{Runtime, RuntimeCall, RuntimeOrigin, Balances}; use up_common::types::{AccountId, Balance}; use fp_self_contained::SelfContainedCall; -use pallet_unique_scheduler::DispatchCall; +use pallet_unique_scheduler_v2::DispatchCall; +use pallet_transaction_payment::ChargeTransactionPayment; + +// type SponsorshipChargeTransactionPayment = +// pallet_charge_transaction::ChargeTransactionPayment; /// The SignedExtension to the basic transaction logic. pub type SignedExtraScheduler = ( @@ -36,6 +41,7 @@ frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, + ChargeTransactionPayment, ); fn get_signed_extras(from: ::AccountId) -> SignedExtraScheduler { @@ -47,95 +53,148 @@ from, )), frame_system::CheckWeight::::new(), - // sponsoring transaction logic - // pallet_charge_transaction::ChargeTransactionPayment::::new(0), + ChargeTransactionPayment::::from(0), ) } pub struct SchedulerPaymentExecutor; -impl +impl DispatchCall for SchedulerPaymentExecutor where - ::Call: Member - + Dispatchable + ::RuntimeCall: Member + + Dispatchable + SelfContainedCall + GetDispatchInfo + From>, SelfContainedSignedInfo: Send + Sync + 'static, - Call: From<::Call> - + From<::Call> + RuntimeCall: From<::RuntimeCall> + + From<::RuntimeCall> + SelfContainedCall, sp_runtime::AccountId32: From<::AccountId>, { fn dispatch_call( - signer: ::AccountId, - call: ::Call, + signer: Option<::AccountId>, + call: ::RuntimeCall, ) -> Result< Result>, TransactionValidityError, > { let dispatch_info = call.get_dispatch_info(); + let len = call.encoded_size(); + + let signed = match signer { + Some(signer) => fp_self_contained::CheckedSignature::Signed( + signer.clone().into(), + get_signed_extras(signer.into()), + ), + None => fp_self_contained::CheckedSignature::Unsigned, + }; + let extrinsic = fp_self_contained::CheckedExtrinsic::< AccountId, - Call, + RuntimeCall, SignedExtraScheduler, SelfContainedSignedInfo, > { - signed: fp_self_contained::CheckedSignature::< - AccountId, - SignedExtraScheduler, - SelfContainedSignedInfo, - >::Signed(signer.clone().into(), get_signed_extras(signer.into())), + signed, function: call.into(), }; - extrinsic.apply::(&dispatch_info, 0) + extrinsic.apply::(&dispatch_info, len) } +} - fn reserve_balance( - id: [u8; 16], - sponsor: ::AccountId, - call: ::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()); +// impl +// DispatchCall for SchedulerPaymentExecutor +// where +// ::Call: Member +// + Dispatchable +// + SelfContainedCall +// + GetDispatchInfo +// + From>, +// SelfContainedSignedInfo: Send + Sync + 'static, +// Call: From<::Call> +// + From<::Call> +// + SelfContainedCall, +// sp_runtime::AccountId32: From<::AccountId>, +// { +// fn dispatch_call( +// signer: Option<::AccountId>, +// call: ::Call, +// ) -> Result< +// Result>, +// TransactionValidityError, +// > { +// let dispatch_info = call.get_dispatch_info(); +// let len = call.encoded_size(); - >::reserve_named( - &id, - &(sponsor.into()), - weight, - ) - } +// let signed = match signer { +// Some(signer) => fp_self_contained::CheckedSignature::Signed( +// signer.clone().into(), +// get_signed_extras(signer.into()), +// ), +// None => fp_self_contained::CheckedSignature::Unsigned, +// }; - fn pay_for_call( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - ) -> Result { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - weight, - ), - ) - } +// let extrinsic = fp_self_contained::CheckedExtrinsic::< +// AccountId, +// Call, +// SignedExtraScheduler, +// SelfContainedSignedInfo, +// > { +// signed, +// function: call.into(), +// }; + +// extrinsic.apply::(&dispatch_info, len) +// } - fn cancel_reserve( - id: [u8; 16], - sponsor: ::AccountId, - ) -> Result { - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - u128::MAX, - ), - ) - } -} +// fn reserve_balance( +// id: [u8; 16], +// sponsor: ::AccountId, +// call: ::Call, +// count: u32, +// ) -> Result<(), DispatchError> { +// let dispatch_info = call.get_dispatch_info(); +// let weight: Balance = +// SponsorshipChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0) +// .saturating_mul(count.into()); + +// >::reserve_named( +// &id, +// &(sponsor.into()), +// weight, +// ) +// } + +// fn pay_for_call( +// id: [u8; 16], +// sponsor: ::AccountId, +// call: ::Call, +// ) -> Result { +// let dispatch_info = call.get_dispatch_info(); +// let weight: Balance = +// SponsorshipChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); +// Ok( +// >::unreserve_named( +// &id, +// &(sponsor.into()), +// weight, +// ), +// ) +// } + +// fn cancel_reserve( +// id: [u8; 16], +// sponsor: ::AccountId, +// ) -> Result { +// Ok( +// >::unreserve_named( +// &id, +// &(sponsor.into()), +// u128::MAX, +// ), +// ) +// } +// } --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -42,6 +42,7 @@ 'pallet-inflation/runtime-benchmarks', 'pallet-app-promotion/runtime-benchmarks', 'pallet-unique-scheduler/runtime-benchmarks', + 'pallet-unique-scheduler-v2/runtime-benchmarks', 'pallet-xcm/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', 'xcm-builder/runtime-benchmarks', @@ -140,6 +141,7 @@ 'pallet-proxy-rmrk-equip/std', 'pallet-unique/std', 'pallet-unique-scheduler/std', + 'pallet-unique-scheduler-v2/std', 'pallet-charge-transaction/std', 'up-data-structs/std', 'sp-api/std', @@ -167,9 +169,11 @@ "orml-xtokens/std", "orml-traits/std", "pallet-foreign-assets/std", + + 'pallet-test-utils?/std', ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] -opal-runtime = ['refungible', 'rmrk', 'app-promotion', 'foreign-assets'] +opal-runtime = ['refungible', 'scheduler', 'rmrk', 'app-promotion', 'foreign-assets', 'pallet-test-utils'] refungible = [] scheduler = [] @@ -470,6 +474,7 @@ pallet-proxy-rmrk-equip = { default-features = false, path = "../../pallets/proxy-rmrk-equip", package = "pallet-rmrk-equip" } pallet-unique-scheduler = { path = '../../pallets/scheduler', default-features = false } pallet-charge-transaction = { git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.30", package = "pallet-template-transaction-payment", default-features = false, version = "3.0.0" } +pallet-unique-scheduler-v2 = { path = '../../pallets/scheduler-v2', default-features = false } pallet-evm-migration = { path = '../../pallets/evm-migration', default-features = false } pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false } pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false } @@ -484,6 +489,11 @@ pallet-foreign-assets = { default-features = false, path = "../../pallets/foreign-assets" } ################################################################################ +# Test dependencies + +pallet-test-utils = { optional = true, default-features = false, path = "../../test-pallets/utils" } + +################################################################################ # Other Dependencies impl-trait-for-tuples = "0.2.2" --- /dev/null +++ b/test-pallets/utils/Cargo.lock @@ -0,0 +1,2585 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1079fb8528d9f9c888b1e8aa651e6e079ade467323d58f75faf1d30b1808f540" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.6", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.6", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.6", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" + +[[package]] +name = "futures-executor" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" + +[[package]] +name = "futures-macro" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" + +[[package]] +name = "futures-task" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" + +[[package]] +name = "futures-util" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.6", + "hmac 0.8.1", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", +] + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory-db" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +dependencies = [ + "hash-db", + "hashbrown", + "parity-util-mem", +] + +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" + +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa 0.4.8", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" +dependencies = [ + "cfg-if", + "hashbrown", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scale-info" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.6", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +dependencies = [ + "itoa 1.0.3", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-debug-derive", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "base58", + "bitflags", + "blake2-rfc", + "byteorder", + "dyn-clonable", + "ed25519-dalek", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde", + "lazy_static", + "libsecp256k1", + "log", + "merlin", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot", + "primitive-types", + "rand 0.7.3", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "wasmi", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.2", + "sha3", + "sp-std", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "futures", + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "parking_lot", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-trie", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keystore" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "async-trait", + "futures", + "merlin", + "parity-scale-codec", + "parking_lot", + "schnorrkel", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-panic-handler" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "rand 0.7.3", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std", + "sp-trie", + "thiserror", + "tracing", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-std", + "thiserror", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.27#8eff668a42325aeb4433eace1604f4d286a6ec05" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmi", +] + +[[package]] +name = "ss58-registry" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "test-pallet-rollback-changes" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "thiserror" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +dependencies = [ + "hash-db", + "hashbrown", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.3", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasmi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" +dependencies = [ + "downcast-rs", + "libc", + "memory_units", + "num-rational", + "num-traits", + "parity-wasm", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] --- /dev/null +++ b/test-pallets/utils/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-test-utils" +version = "0.1.0" +license = "GPLv3" +edition = "2021" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +# pallet-unique-scheduler = { path = '../../pallets/scheduler', default-features = false } +pallet-unique-scheduler-v2 = { path = '../../pallets/scheduler-v2', default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "pallet-unique-scheduler-v2/std", +] --- /dev/null +++ b/test-pallets/utils/src/lib.rs @@ -0,0 +1,125 @@ +// 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use pallet_unique_scheduler_v2::{TaskName, Pallet as SchedulerPallet}; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_unique_scheduler_v2::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ValueIsSet, + ShouldRollback, + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn is_enabled)] + pub type Enabled = StorageValue<_, bool, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn test_value)] + pub type TestValue = StorageValue<_, u32, ValueQuery>; + + #[pallet::error] + pub enum Error { + TestPalletDisabled, + TriggerRollback, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(10_000)] + pub fn enable(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + >::set(true); + + Ok(()) + } + + #[pallet::weight(10_000)] + pub fn set_test_value(origin: OriginFor, value: u32) -> DispatchResult { + Self::ensure_origin_and_enabled(origin)?; + + >::put(value); + + Self::deposit_event(Event::ValueIsSet); + + Ok(()) + } + + #[pallet::weight(10_000)] + pub fn set_test_value_and_rollback(origin: OriginFor, value: u32) -> DispatchResult { + Self::set_test_value(origin, value)?; + + Self::deposit_event(Event::ShouldRollback); + + Err(>::TriggerRollback.into()) + } + + #[pallet::weight(10_000)] + pub fn inc_test_value(origin: OriginFor) -> DispatchResult { + Self::set_test_value(origin, >::get() + 1) + } + + #[pallet::weight(10_000)] + pub fn self_canceling_inc( + origin: OriginFor, + id: TaskName, + max_test_value: u32, + ) -> DispatchResult { + Self::ensure_origin_and_enabled(origin.clone())?; + Self::inc_test_value(origin.clone())?; + + if >::get() == max_test_value { + SchedulerPallet::::cancel_named(origin, id)?; + } + + Ok(()) + } + + #[pallet::weight(100_000_000)] + pub fn just_take_fee(origin: OriginFor) -> DispatchResult { + Self::ensure_origin_and_enabled(origin)?; + Ok(()) + } + } +} + +impl Pallet { + fn ensure_origin_and_enabled(origin: OriginFor) -> DispatchResult { + ensure_signed(origin)?; + >::get() + .then(|| ()) + .ok_or(>::TestPalletDisabled.into()) + } +} --- a/tests/src/.outdated/eth/scheduling.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -// 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 . - -import {expect} from 'chai'; -import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from '../../deprecated-helpers/eth/helpers'; -import {scheduleExpectSuccess, waitNewBlocks, requirePallets, Pallets} from '../../deprecated-helpers/helpers'; - -// TODO mrshiposha update this test in #581 -describe.skip('Scheduing EVM smart contracts', () => { - before(async function() { - await requirePallets(this, [Pallets.Scheduler]); - }); - - itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => { - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, deployer); - const initialValue = await flipper.methods.getValue().call(); - const alice = privateKeyWrapper('//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); - } - }); -}); --- a/tests/src/.outdated/scheduler.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -// 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 . - -import chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -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 '../deprecated-helpers/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; - -chai.use(chaiAsPromised); - -// todo:playgrounds skipped ~ postponed -describe.skip('Scheduling token and balance transfers', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let scheduledIdBase: string; - let scheduledIdSlider: number; - - before(async() => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//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 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, privateKeyWrapper) => { - // Find an empty, unused account - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - 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, privateKeyWrapper) => { - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - 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, privateKeyWrapper) => { - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - 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); - }); - }); -}); --- /dev/null +++ b/tests/src/eth/scheduling.test.ts @@ -0,0 +1,62 @@ +// 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 . + +import {expect} from 'chai'; +import {EthUniqueHelper, itEth} from './util'; +import {Pallets, usingPlaygrounds} from '../util'; + +describe('Scheduing EVM smart contracts', () => { + + before(async () => { + await usingPlaygrounds(async (helper) => { + await helper.testUtils.enable(); + }); + }); + + itEth.ifWithPallets('Successfully schedules and periodically executes an EVM contract', [Pallets.Scheduler], async ({helper, privateKey}) => { + const alice = await privateKey('//Alice'); + + const scheduledId = await helper.arrange.makeScheduledId(); + + const deployer = await helper.eth.createAccountWithBalance(alice); + const flipper = await helper.eth.deployFlipper(deployer); + + const initialValue = await flipper.methods.getValue().call(); + await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address)); + + const waitForBlocks = 4; + const periodic = { + period: 2, + repetitions: 2, + }; + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .eth.sendEVM( + alice, + flipper.options.address, + flipper.methods.flip().encodeABI(), + '0', + ); + + expect(await flipper.methods.getValue().call()).to.be.equal(initialValue); + + await helper.wait.newBlocks(waitForBlocks + 1); + expect(await flipper.methods.getValue().call()).to.be.not.equal(initialValue); + + await helper.wait.newBlocks(periodic.period); + expect(await flipper.methods.getValue().call()).to.be.equal(initialValue); + }); +}); --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -174,20 +174,20 @@ async callEVM(signer: TEthereumAccount, contractAddress: string, abi: string) { return await this.helper.callRpc('api.rpc.eth.call', [{from: signer, to: contractAddress, data: abi}]); } - + async createCollecion(functionName: string, signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice(); const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); - + const result = await collectionHelper.methods[functionName](name, description, tokenPrefix).send({value: Number(collectionCreationPrice)}); const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); const events = this.helper.eth.normalizeEvents(result.events); - + return {collectionId, collectionAddress, events}; } - + async createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { return this.createCollecion('createNFTCollection', signer, name, description, tokenPrefix); } @@ -205,20 +205,20 @@ async createRFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string, events: NormalizedEvent[]}> { return this.createCollecion('createRFTCollection', signer, name, description, tokenPrefix); } - + async createFungibleCollection(signer: string, name: string, decimals: number, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[]}> { const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice(); const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); - + const result = await collectionHelper.methods.createFTCollection(name, decimals, description, tokenPrefix).send({value: Number(collectionCreationPrice)}); const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); - + const events = this.helper.eth.normalizeEvents(result.events); - + return {collectionId, collectionAddress, events}; } - + async createERC721MetadataCompatibleRFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); @@ -349,15 +349,6 @@ normalizeAddress(address: string): string { return '0x' + address.substring(address.length - 40); - } -} - -export class EthPropertyGroup extends EthGroupBase { - property(key: string, value: string): EthProperty { - return [ - key, - '0x'+Buffer.from(value).toString('hex'), - ]; } } export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper; --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -62,10 +62,11 @@ const chain = await helper.callRpc('api.rpc.system.chain', []); const refungible = 'refungible'; - // const scheduler = 'scheduler'; + const scheduler = 'scheduler'; const foreignAssets = 'foreignassets'; const rmrkPallets = ['rmrkcore', 'rmrkequip']; const appPromotion = 'apppromotion'; + const testUtils = 'testutils'; if (chain.eq('OPAL by UNIQUE')) { requiredPallets.push( @@ -73,6 +74,8 @@ // scheduler, foreignAssets, appPromotion, + testUtils, + scheduler, ...rmrkPallets, ); } else if (chain.eq('QUARTZ by UNIQUE')) { --- /dev/null +++ b/tests/src/scheduler.test.ts @@ -0,0 +1,647 @@ +// 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 . + +import {expect, itSub, Pallets, usingPlaygrounds} from './util'; +import {IKeyringPair} from '@polkadot/types/types'; +import {DevUniqueHelper} from './util/playgrounds/unique.dev'; + +describe('Scheduling token and balance transfers', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKeyWrapper) => { + alice = await privateKeyWrapper('//Alice'); + bob = await privateKeyWrapper('//Bob'); + charlie = await privateKeyWrapper('//Charlie'); + + await helper.testUtils.enable(); + }); + }); + + itSub.ifWithPallets('Can delay a transfer of an owned token', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + const schedulerId = await helper.arrange.makeScheduledId(); + const blocksBeforeExecution = 4; + + await token.scheduleAfter(schedulerId, blocksBeforeExecution) + .transfer(alice, {Substrate: bob.address}); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + + await helper.wait.newBlocks(blocksBeforeExecution + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets('Can transfer funds periodically', [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 1; + + const amount = 1n * helper.balance.getOneTokenNominal(); + const periodic = { + period: 2, + repetitions: 2, + }; + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .balance.transferToSubstrate(alice, bob.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterFirst) + .to.be.equal( + bobsBalanceBefore + 1n * amount, + '#1 Balance of the recipient should be increased by 1 * amount', + ); + + await helper.wait.newBlocks(periodic.period); + + const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterSecond) + .to.be.equal( + bobsBalanceBefore + 2n * amount, + '#2 Balance of the recipient should be increased by 2 * amount', + ); + }); + + itSub.ifWithPallets('Can cancel a scheduled operation which has not yet taken effect', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + await helper.scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + }); + + itSub.ifWithPallets('Can cancel a periodic operation (transfer of funds)', [Pallets.Scheduler], async ({helper}) => { + const waitForBlocks = 1; + const periodic = { + period: 3, + repetitions: 2, + }; + + const scheduledId = await helper.arrange.makeScheduledId(); + + const amount = 1n * helper.balance.getOneTokenNominal(); + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .balance.transferToSubstrate(alice, bob.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); + + expect(bobsBalanceAfterFirst) + .to.be.equal( + bobsBalanceBefore + 1n * amount, + '#1 Balance of the recipient should be increased by 1 * amount', + ); + + await helper.scheduler.cancelScheduled(alice, scheduledId); + await helper.wait.newBlocks(periodic.period); + + const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterSecond) + .to.be.equal( + bobsBalanceAfterFirst, + '#2 Balance of the recipient should not be changed', + ); + }); + + itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const initTestVal = 42; + const changedTestVal = 111; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks) + .testUtils.setTestValueAndRollback(alice, changedTestVal); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const testVal = await helper.testUtils.testValue(); + expect(testVal, 'The test value should NOT be commited') + .to.be.equal(initTestVal); + }); + + itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.Scheduler, Pallets.TestUtils], async function({helper}) { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + const periodic = { + period: 2, + repetitions: 2, + }; + + const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []); + const scheduledLen = dummyTx.callIndex.length; + + const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen)) + .partialFee.toBigInt(); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .testUtils.justTakeFee(alice); + + await helper.wait.newBlocks(1); + + const aliceInitBalance = await helper.balance.getSubstrate(alice.address); + let diff; + + await helper.wait.newBlocks(waitForBlocks); + + const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address); + expect( + aliceBalanceAfterFirst < aliceInitBalance, + '[after execution #1] Scheduled task should take a fee', + ).to.be.true; + + diff = aliceInitBalance - aliceBalanceAfterFirst; + expect(diff).to.be.equal( + expectedScheduledFee, + 'Scheduled task should take the right amount of fees', + ); + + await helper.wait.newBlocks(periodic.period); + + const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address); + expect( + aliceBalanceAfterSecond < aliceBalanceAfterFirst, + '[after execution #2] Scheduled task should take a fee', + ).to.be.true; + + diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond; + expect(diff).to.be.equal( + expectedScheduledFee, + 'Scheduled task should take the right amount of fees', + ); + }); + + // Check if we can cancel a scheduled periodic operation + // in the same block in which it is running + itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const currentBlockNumber = await helper.chain.getLatestBlockNumber(); + const blocksBeforeExecution = 10; + const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; + + const [ + scheduledId, + scheduledCancelId, + ] = await helper.arrange.makeScheduledIds(2); + + const periodic = { + period: 5, + repetitions: 5, + }; + + const initTestVal = 0; + const incTestVal = initTestVal + 1; + const finalTestVal = initTestVal + 2; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAt(scheduledId, firstExecutionBlockNumber, {periodic}) + .testUtils.incTestValue(alice); + + // Cancel the inc tx after 2 executions + // *in the same block* in which the second execution is scheduled + await helper.scheduler.scheduleAt( + scheduledCancelId, + firstExecutionBlockNumber + periodic.period, + ).scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.newBlocks(blocksBeforeExecution); + + // execution #0 + expect(await helper.testUtils.testValue()) + .to.be.equal(incTestVal); + + await helper.wait.newBlocks(periodic.period); + + // execution #1 + expect(await helper.testUtils.testValue()) + .to.be.equal(finalTestVal); + + for (let i = 1; i < periodic.repetitions; i++) { + await helper.wait.newBlocks(periodic.period); + expect(await helper.testUtils.testValue()) + .to.be.equal(finalTestVal); + } + }); + + itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + const periodic = { + period: 2, + repetitions: 5, + }; + + const initTestVal = 0; + const maxTestVal = 2; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal); + + await helper.wait.newBlocks(waitForBlocks + 1); + + // execution #0 + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 1); + + await helper.wait.newBlocks(periodic.period); + + // execution #1 + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 2); + + await helper.wait.newBlocks(periodic.period); + + // + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 2); + }); + + itSub.ifWithPallets('Root can cancel any scheduled operation', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets('Root can set prioritized scheduled operation', [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const amount = 42n * helper.balance.getOneTokenNominal(); + + const balanceBefore = await helper.balance.getSubstrate(charlie.address); + + await helper.getSudo() + .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}) + .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const balanceAfter = await helper.balance.getSubstrate(charlie.address); + + expect(balanceAfter > balanceBefore).to.be.true; + + const diff = balanceAfter - balanceBefore; + expect(diff).to.be.equal(amount); + }); + + itSub.ifWithPallets("Root can change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 6; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + const priority = 112; + await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority); + + const priorityChanged = await helper.wait.event( + waitForBlocks, + 'scheduler', + 'PriorityChanged', + ); + + expect(priorityChanged !== null).to.be.true; + expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString()); + }); + + itSub.ifWithPallets('Prioritized operations executes in valid order', [Pallets.Scheduler], async ({helper}) => { + const [ + scheduledFirstId, + scheduledSecondId, + ] = await helper.arrange.makeScheduledIds(2); + + const currentBlockNumber = await helper.chain.getLatestBlockNumber(); + const blocksBeforeExecution = 4; + const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; + + const prioHigh = 0; + const prioLow = 255; + + const periodic = { + period: 6, + repetitions: 2, + }; + + const amount = 1n * helper.balance.getOneTokenNominal(); + + // Scheduler a task with a lower priority first, then with a higher priority + await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic}) + .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); + + await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic}) + .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); + + const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched'); + + await helper.wait.newBlocks(blocksBeforeExecution); + + // Flip priorities + await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh); + await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow); + + await helper.wait.newBlocks(periodic.period); + + const dispatchEvents = capture.extractCapturedEvents(); + expect(dispatchEvents.length).to.be.equal(4); + + const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString()); + + const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]]; + const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]]; + + expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId); + expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId); + + expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId); + expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId); + }); +}); + +describe('Negative Test: Scheduling', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKeyWrapper) => { + alice = await privateKeyWrapper('//Alice'); + bob = await privateKeyWrapper('//Bob'); + + await helper.testUtils.enable(); + }); + }); + + itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks); + await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal())) + .to.be.rejectedWith(/scheduler\.FailedToSchedule/); + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter); + }); + + itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + await expect(helper.scheduler.cancelScheduled(alice, scheduledId)) + .to.be.rejectedWith(/scheduler\.NotFound/); + }); + + itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + await expect(helper.scheduler.cancelScheduled(bob, scheduledId)) + .to.be.rejectedWith(/BadOrigin/); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const amount = 42n * helper.balance.getOneTokenNominal(); + + const balanceBefore = await helper.balance.getSubstrate(bob.address); + + const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}); + + await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount)) + .to.be.rejectedWith(/BadOrigin/); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const balanceAfter = await helper.balance.getSubstrate(bob.address); + + expect(balanceAfter).to.be.equal(balanceBefore); + }); + + itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + const priority = 112; + await expect(helper.scheduler.changePriority(alice, scheduledId, priority)) + .to.be.rejectedWith(/BadOrigin/); + + const priorityChanged = await helper.wait.event( + waitForBlocks, + 'scheduler', + 'PriorityChanged', + ); + + expect(priorityChanged === null).to.be.true; + }); +}); + +// Implementation of the functionality tested here was postponed/shelved +describe.skip('Sponsoring scheduling', () => { + // let alice: IKeyringPair; + // let bob: IKeyringPair; + + // before(async() => { + // await usingApi(async (_, privateKeyWrapper) => { + // alice = privateKeyWrapper('//Alice'); + // bob = privateKeyWrapper('//Bob'); + // }); + // }); + + it('Can sponsor scheduling a transaction', async () => { + // const collectionId = await createCollectionExpectSuccess(); + // await setCollectionSponsorExpectSuccess(collectionId, bob.address); + // await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); + + // await usingApi(async api => { + // const scheduledId = await makeScheduledId(); + // 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(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId); + // 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, privateKeyWrapper) => { + // // Find an empty, unused account + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + // 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'); + // const scheduledId = await makeScheduledId(); + + // // Schedule transfer of the NFT a few blocks ahead + // const waitForBlocks = 5; + // await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId); + + // // 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, privateKeyWrapper) => { + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); + // await submitTransactionAsync(alice, balanceTx); + + // await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address); + // await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance); + + // const scheduledId = await makeScheduledId(); + // const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + // const waitForBlocks = 5; + // await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId); + + // 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('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, privateKeyWrapper) => { + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + // 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 scheduledId = await makeScheduledId(); + + // /*const badTransaction = async function () { + // await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); + // }; + // await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/ + + // await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/); + + // expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore); + // }); + }); +}); --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -95,6 +95,7 @@ NFT = 'nonfungible', Scheduler = 'scheduler', AppPromotion = 'apppromotion', + TestUtils = 'testutils', } export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: string[]) { --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -8,8 +8,10 @@ import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; import {EventRecord} from '@polkadot/types/interfaces'; -import {ICrossAccountId} from './types'; +import {ICrossAccountId, TSigner} from './types'; import {FrameSystemEventRecord} from '@polkadot/types/lookup'; +import {VoidFn} from '@polkadot/api/types'; +import {Pallets} from '..'; export class SilentLogger { log(_msg: any, _level: any): void { } @@ -63,6 +65,7 @@ arrange: ArrangeGroup; wait: WaitGroup; admin: AdminGroup; + testUtils: TestUtilGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { options.helperBase = options.helperBase ?? DevUniqueHelper; @@ -71,6 +74,7 @@ this.arrange = new ArrangeGroup(this); this.wait = new WaitGroup(this); this.admin = new AdminGroup(this); + this.testUtils = new TestUtilGroup(this); } async connect(wsEndpoint: string, _listeners?: any): Promise { @@ -154,6 +158,8 @@ class ArrangeGroup { helper: DevUniqueHelper; + scheduledIdSlider = 0; + constructor(helper: DevUniqueHelper) { this.helper = helper; } @@ -297,6 +303,39 @@ const address = stringToU8a(('modl' + palletId).padEnd(32, '\0')); return encodeAddress(address); } + + async makeScheduledIds(num: number): Promise { + await this.helper.wait.noScheduledTasks(); + + function makeId(slider: number) { + const scheduledIdSize = 64; + const hexId = slider.toString(16); + const prefixSize = scheduledIdSize - hexId.length; + + const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId; + + return scheduledId; + } + + const ids = []; + for (let i = 0; i < num; i++) { + ids.push(makeId(this.scheduledIdSlider)); + this.scheduledIdSlider += 1; + } + + return ids; + } + + async makeScheduledId(): Promise { + return (await this.makeScheduledIds(1))[0]; + } + + async captureEvents(eventSection: string, eventMethod: string): Promise { + const capture = new EventCapture(this.helper, eventSection, eventMethod); + await capture.startCapture(); + + return capture; + } } class MoonbeamAccountGroup { @@ -389,6 +428,24 @@ }); } + async noScheduledTasks() { + const api = this.helper.getApi(); + + // eslint-disable-next-line no-async-promise-executor + const promise = new Promise(async resolve => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => { + const areThereScheduledTasks = await api.query.scheduler.lookup.entries(); + + if(areThereScheduledTasks.length == 0) { + unsubscribe(); + resolve(); + } + }); + }); + + return promise; + } + async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) { // eslint-disable-next-line no-async-promise-executor const promise = new Promise(async (resolve) => { @@ -414,7 +471,6 @@ maxBlocksToWait--; } else { this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`); - unsubscribe(); resolve(null); } @@ -424,6 +480,86 @@ } } +class TestUtilGroup { + helper: DevUniqueHelper; + + constructor(helper: DevUniqueHelper) { + this.helper = helper; + } + + async enable() { + if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) { + return; + } + + const signer = this.helper.util.fromSeed('//Alice'); + await this.helper.getSudo().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true); + } + + async setTestValue(signer: TSigner, testVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true); + } + + async incTestValue(signer: TSigner) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true); + } + + async setTestValueAndRollback(signer: TSigner, testVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true); + } + + async testValue() { + return (await this.helper.callRpc('api.query.testUtils.testValue', [])).toNumber(); + } + + async justTakeFee(signer: TSigner) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true); + } + + async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) { + await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true); + } +} + +class EventCapture { + helper: DevUniqueHelper; + eventSection: string; + eventMethod: string; + events: EventRecord[] = []; + unsubscribe: VoidFn | null = null; + + constructor( + helper: DevUniqueHelper, + eventSection: string, + eventMethod: string, + ) { + this.helper = helper; + this.eventSection = eventSection; + this.eventMethod = eventMethod; + } + + async startCapture() { + this.stopCapture(); + this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => { + const newEvents = eventRecords.filter(r => { + return r.event.section == this.eventSection && r.event.method == this.eventMethod; + }); + + this.events.push(...newEvents); + })) as any; + } + + stopCapture() { + if (this.unsubscribe !== null) { + this.unsubscribe(); + } + } + + extractCapturedEvents() { + return this.events; + } +} + class AdminGroup { helper: UniqueHelper; --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -39,6 +39,7 @@ MoonbeamAssetInfo, DemocracyStandardAccountVote, } from './types'; +import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; export class CrossAccountId implements ICrossAccountId { Substrate?: TSubstrateAccount; @@ -67,7 +68,7 @@ static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId { return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)}); } - + withNormalizedSubstrate(ss58Format = 42): CrossAccountId { if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format); return this; @@ -90,7 +91,7 @@ if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)}); return this; } - + toLowerCase(): CrossAccountId { if (this.Substrate) this.Substrate = this.Substrate.toLowerCase(); if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase(); @@ -190,7 +191,7 @@ } static extractTokensFromCreationResult(creationResult: ITransactionResult): { - success: boolean, + success: boolean, tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[], } { if (creationResult.status !== this.transactionStatus.SUCCESS) { @@ -214,7 +215,7 @@ } static extractTokensFromBurnResult(burnResult: ITransactionResult): { - success: boolean, + success: boolean, tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[], } { if (burnResult.status !== this.transactionStatus.SUCCESS) { @@ -285,7 +286,7 @@ static bigIntToDecimals(number: bigint, decimals = 18) { const numberStr = number.toString(); const dotPos = numberStr.length - decimals; - + if (dotPos <= 0) { return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr; } else { @@ -315,7 +316,7 @@ return obj; } - + private static extractData(data: any, type: any): any { if(!type) return data.toHuman(); if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber(); @@ -561,6 +562,26 @@ }); } + async getPaymentInfo(signer: TSigner, tx: any, len: number | null) { + const api = this.getApi(); + const signingInfo = await api.derive.tx.signingInfo(signer.address); + + // We need to sign the tx because + // unsigned transactions does not have an inclusion fee + tx.sign(signer, { + blockHash: api.genesisHash, + genesisHash: api.genesisHash, + runtimeVersion: api.runtimeVersion, + nonce: signingInfo.nonce, + }); + + if (len === null) { + return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo; + } else { + return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo; + } + } + constructApiCall(apiCall: string, params: any[]) { if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`); let call = this.getApi() as any; @@ -935,7 +956,7 @@ /** * Check if user is in allow list. - * + * * @param collectionId ID of collection * @param user Account to check * @example await getAdmins(1) @@ -1045,7 +1066,7 @@ /** * Get collection properties. - * + * * @param collectionId ID of collection * @param propertyKeys optionally filter the returned properties to only these keys * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']); @@ -1266,8 +1287,8 @@ if (tokenData === null || tokenData.owner === null) return null; const owner = {} as any; for (const key of Object.keys(tokenData.owner)) { - owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate' - ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key]) + owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate' + ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key]) : tokenData.owner[key]; } tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner); @@ -1297,7 +1318,7 @@ /** * Get token property permissions. - * + * * @param collectionId ID of collection * @param propertyKeys optionally filter the returned property permissions to only these keys * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']); @@ -1329,7 +1350,7 @@ /** * Get properties, metadata assigned to a token. - * + * * @param collectionId ID of collection * @param tokenId ID of token * @param propertyKeys optionally filter the returned properties to only these keys @@ -2164,8 +2185,8 @@ }; } }); - const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from - && this.helper.address.normalizeSubstrate(address) === transfer.to + const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from + && this.helper.address.normalizeSubstrate(address) === transfer.to && BigInt(amount) === transfer.amount; return isSuccess; } @@ -2213,8 +2234,8 @@ }; } }); - const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from - && address === transfer.to + const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from + && address === transfer.to && BigInt(amount) === transfer.amount; return isSuccess; } @@ -2283,6 +2304,25 @@ async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise { return this.subBalanceGroup.transferToSubstrate(signer, address, amount); } + + async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true); + + let transfer = {from: null, to: null, amount: 0n} as any; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'balances') && (method === 'Transfer')) { + transfer = { + from: this.helper.address.normalizeSubstrate(data[0]), + to: this.helper.address.normalizeSubstrate(data[1]), + amount: BigInt(data[2]), + }; + } + }); + let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from; + isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to; + isSuccess = isSuccess && BigInt(amount) === transfer.amount; + return isSuccess; + } } class AddressGroup extends HelperGroup { @@ -2393,7 +2433,7 @@ async getTotalStakedPerBlock(address: ICrossAccountId): Promise { const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]); return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => { - return { + return { block: block.toBigInt(), amount: amount.toBigInt(), }; @@ -2798,7 +2838,7 @@ this.blocksNum, this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, this.options.priority ?? null, - {Value: scheduledTx}, + scheduledTx, ], expectSuccess, ); @@ -2820,7 +2860,6 @@ expectSuccess?: boolean, ): Promise { const call = this.constructApiCall(extrinsic, params); - return super.executeExtrinsic( sender, 'api.tx.sudo.sudo',