difftreelog
Scheduler refactored and fixed
in: master
8 files changed
pallets/nft-charge-transaction/src/lib.rsdiffbeforeafterboth--- a/pallets/nft-charge-transaction/src/lib.rs
+++ b/pallets/nft-charge-transaction/src/lib.rs
@@ -135,6 +135,13 @@
.map(|i| (fee, i));
}
+ // check errors
+ let error = <pallet_nft_transaction_payment::Module<T>>::check_error(who, call);
+ match error {
+ Err(error) => return Err(error),
+ Ok(error) => {}
+ };
+
// Determine who is paying transaction fee based on ecnomic model
// Parse call to extract collection ID and access collection sponsor
let sponsor = <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
pallets/nft-transaction-payment/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/README.md
@@ -0,0 +1,13 @@
+# Nft Transaction Payment
+
+## Overview
+
+A module containing the sponsoring logic for paying for sponsored collections
+
+**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.
pallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth--- a/pallets/nft-transaction-payment/src/lib.rs
+++ b/pallets/nft-transaction-payment/src/lib.rs
@@ -31,6 +31,9 @@
traits::{
Hash, Dispatchable,
},
+ transaction_validity::{
+ InvalidTransaction, TransactionValidityError,
+ },
};
use pallet_contracts::chain_extension::UncheckedFrom;
use sp_std::prelude::*;
@@ -85,6 +88,36 @@
impl<T: Config> Module<T>
{
+ pub fn check_error(
+ who: &T::AccountId,
+ call: &T::Call
+ ) -> Result<bool, TransactionValidityError> where
+ T::Call: Dispatchable<Info=DispatchInfo>,
+ T::Call: IsSubType<pallet_nft::Call<T>>,
+ T::Call: IsSubType<pallet_contracts::Call<T>>,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>
+ {
+
+ match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
+ Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
+
+ let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());
+
+ let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);
+ let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
+
+ if !owned_contract && white_list_enabled {
+ if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
+ return Err(InvalidTransaction::Call.into());
+ }
+ }
+ Ok(true)
+ },
+ _ => { Ok(true) },
+ }
+ }
+
pub fn withdraw_type(
who: &T::AccountId,
call: &T::Call
@@ -366,160 +399,4 @@
None
}
-}
-
-// type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
-
-// /// Require the transactor pay for themselves and maybe include a tip to gain additional priority
-// /// in the queue.
-// #[derive(Encode, Decode, Clone, Eq, PartialEq)]
-// pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
-
-// impl<T: Config + Send + Sync> sp_std::fmt::Debug
-// for ChargeTransactionPayment<T>
-// {
-// #[cfg(feature = "std")]
-// fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
-// write!(f, "ChargeTransactionPayment<{:?}>", self.0)
-// }
-// #[cfg(not(feature = "std"))]
-// fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
-// Ok(())
-// }
-// }
-
-// impl<T: Config> ChargeTransactionPayment<T>
-// where
-// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
-// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-// T::AccountId: AsRef<[u8]>,
-// T::AccountId: UncheckedFrom<T::Hash>,
-// {
-// fn traditional_fee(
-// len: usize,
-// info: &DispatchInfoOf<T::Call>,
-// tip: BalanceOf<T>,
-// ) -> BalanceOf<T>
-// where
-// T::Call: Dispatchable<Info = DispatchInfo>,
-// {
-// <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)
-// }
-
-// fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {
-// let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);
-// let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);
-// let len_saturation = max_block_length as u64 / (len as u64).max(1);
-// let coefficient: BalanceOf<T> = weight_saturation
-// .min(len_saturation)
-// .saturated_into::<BalanceOf<T>>();
-// final_fee
-// .saturating_mul(coefficient)
-// .saturated_into::<TransactionPriority>()
-// }
-
-// fn withdraw_fee(
-// &self,
-// who: &T::AccountId,
-// call: &T::Call,
-// info: &DispatchInfoOf<T::Call>,
-// len: usize,
-// ) -> Result<
-// (
-// BalanceOf<T>,
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
-// ),
-// TransactionValidityError,
-// > {
-// let tip = self.0;
-
-// let fee = Self::traditional_fee(len, info, tip);
-
-// // Only mess with balances if fee is not zero.
-// if fee.is_zero() {
-// return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)
-// .map(|i| (fee, i));
-// }
-
-// // Determine who is paying transaction fee based on ecnomic model
-// // Parse call to extract collection ID and access collection sponsor
-
-// let sponsor = pallet_nft_transaction_payment::<T>::withdraw_type(who, call);
-// //let sponsor = Self::Module::<T>::withdraw_type(who, call);;
-// // <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
-
-// let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());
-
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)
-// .map(|i| (fee, i))
-// }
-// }
-
-
-// impl<T: Config + Send + Sync> SignedExtension
-// for ChargeTransactionPayment<T>
-// where
-// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
-// T::AccountId: AsRef<[u8]>,
-// T::AccountId: UncheckedFrom<T::Hash>,
-// {
-// const IDENTIFIER: &'static str = "ChargeTransactionPayment";
-// type AccountId = T::AccountId;
-// type Call = T::Call;
-// type AdditionalSigned = ();
-// type Pre = (
-// // tip
-// BalanceOf<T>,
-// // who pays fee
-// Self::AccountId,
-// // imbalance resulting from withdrawing the fee
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
-// );
-// fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
-// Ok(())
-// }
-
-// fn validate(
-// &self,
-// who: &Self::AccountId,
-// call: &Self::Call,
-// info: &DispatchInfoOf<Self::Call>,
-// len: usize,
-// ) -> TransactionValidity {
-// let (fee, _) = self.withdraw_fee(who, call, info, len)?;
-// Ok(ValidTransaction {
-// priority: Self::get_priority(len, info, fee),
-// ..Default::default()
-// })
-// }
-
-// fn pre_dispatch(
-// self,
-// who: &Self::AccountId,
-// call: &Self::Call,
-// info: &DispatchInfoOf<Self::Call>,
-// len: usize,
-// ) -> Result<Self::Pre, TransactionValidityError> {
-// let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;
-// Ok((self.0, who.clone(), imbalance))
-// }
-
-// fn post_dispatch(
-// pre: Self::Pre,
-// info: &DispatchInfoOf<Self::Call>,
-// post_info: &PostDispatchInfoOf<Self::Call>,
-// len: usize,
-// _result: &DispatchResult,
-// ) -> Result<(), TransactionValidityError> {
-// let (tip, who, imbalance) = pre;
-// let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(
-// len as u32,
-// info,
-// post_info,
-// tip,
-// );
-// <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;
-// Ok(())
-// }
-// }
\ No newline at end of file
+}
\ No newline at end of file
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -32,10 +32,11 @@
use frame_system::{self as system, ensure_signed, ensure_root};
use sp_runtime::sp_std::prelude::Vec;
+use core::ops::{Deref, DerefMut};
use nft_data_structs::{
MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,
AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,
- CollectionId, CollectionMode, CollectionHandle, TokenId,
+ CollectionId, CollectionMode, TokenId,
SchemaVersion, SponsorshipState, Ownership,
NftItemType, FungibleItemType, ReFungibleItemType
};
@@ -164,7 +165,25 @@
}
}
-// + pallet_transaction_payment::Config + pallet_nft_transaction_payment::Config + pallet_contracts::Config
+pub struct CollectionHandle<T: frame_system::Config> {
+ pub id: CollectionId,
+ pub collection: Collection<T>,
+}
+
+impl<T: frame_system::Config> Deref for CollectionHandle<T> {
+ type Target = Collection<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.collection
+ }
+}
+
+impl<T: frame_system::Config> DerefMut for CollectionHandle<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.collection
+ }
+}
+
pub trait Config: system::Config + Sized {
pallets/scheduler/src/lib.rsdiffbeforeafterboth1// This file is part of Substrate.23// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! # Scheduler19//! A module for scheduling dispatches.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Module`]24//!25//! ## Overview26//!27//! This module exposes capabilities for scheduling dispatches to occur at a28//! specified block number or at a specified period. These scheduled dispatches29//! may be named or anonymous and may be canceled.30//!31//! **NOTE:** The scheduled calls will be dispatched with the default filter32//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin33//! except root which will get no filter. And not the filter contained in origin34//! use to call `fn schedule`.35//!36//! If a call is scheduled using proxy or whatever mecanism which adds filter,37//! then those filter will not be used when dispatching the schedule call.38//!39//! ## Interface40//!41//! ### Dispatchable Functions42//!43//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a44//! specified block and with a specified priority.45//! * `cancel` - cancel a scheduled dispatch, specified by block number and46//! index.47//! * `schedule_named` - augments the `schedule` interface with an additional48//! `Vec<u8>` parameter that can be used for identification.49//! * `cancel_named` - the named complement to the cancel function.5051// Ensure we're `no_std` when compiling for Wasm.52#![cfg_attr(not(feature = "std"), no_std)]5354mod benchmarking;55pub mod weights;5657use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow};58use codec::{Encode, Decode, Codec};59use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin, Saturating}};60use frame_support::{61 decl_module, decl_storage, decl_event, decl_error, IterableStorageMap,62 dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter},63 traits::{Get, schedule::{self, DispatchTime}, OriginTrait, EnsureOrigin, IsType},64 weights::{GetDispatchInfo, Weight},65};66use frame_system::{self as system, ensure_signed};67use pallet_nft::*;68// use pallet_nft_transaction_payment::{self as nft_transaction_payment};69use nft_data_structs::*;70pub use weights::WeightInfo;7172/// Our pallet's configuration trait. All our types and constants go in here. If the73/// pallet is dependent on specific other pallets, then their configuration traits74/// should be added to our implied traits list.75///76/// `system::Config` should always be included in our implied traits.77/// // 78pub trait Config: system::Config79{ 8081 /// The overarching event type.82 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;8384 /// The aggregated origin which the dispatch will take.85 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>86 + From<Self::PalletsOrigin> + IsType<<Self as system::Config>::Origin>;8788 /// The caller origin, overarching type of all pallets origins.89 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq;9091 /// The aggregated call type.92 type Call: Parameter + Dispatchable<Origin=<Self as Config>::Origin> + GetDispatchInfo + From<system::Call<Self>>;9394 /// The maximum weight that may be scheduled per block for any dispatchables of less priority95 /// than `schedule::HARD_DEADLINE`.96 type MaximumWeight: Get<Weight>;9798 /// Required origin to schedule or cancel calls.99 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;100101 /// The maximum number of scheduled calls in the queue for a single block.102 /// Not strictly enforced, but used for weight estimation.103 type MaxScheduledPerBlock: Get<u32>;104105 /// TODO106 type Sponsoring: SponsoringResolve<Self::AccountId, <Self as Config>::Call>;107108 /// Weight information for extrinsics in this pallet.109 type WeightInfo: WeightInfo;110}111112// pub type SelfWeightInfo<T> = <T as system::Config>::WeightInfo;113114/// Just a simple index for naming period tasks.115pub type PeriodicIndex = u32;116/// The location of a scheduled task that can be used to remove it.117pub type TaskAddress<BlockNumber> = (BlockNumber, u32);118119#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]120#[derive(Clone, RuntimeDebug, Encode, Decode)]121struct ScheduledV1<Call, BlockNumber> {122 maybe_id: Option<Vec<u8>>,123 priority: schedule::Priority,124 call: Call,125 maybe_periodic: Option<schedule::Period<BlockNumber>>,126}127128/// Information regarding an item to be executed in the future.129#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]130#[derive(Clone, RuntimeDebug, Encode, Decode)]131pub struct ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId> {132 /// The unique identity for this task, if there is one.133 maybe_id: Option<Vec<u8>>,134 /// This task's priority.135 priority: schedule::Priority,136 /// The call to be dispatched.137 call: Call,138 /// If the call is periodic, then this points to the information concerning that.139 maybe_periodic: Option<schedule::Period<BlockNumber>>,140 /// The origin to dispatch the call.141 origin: PalletsOrigin,142 _phantom: PhantomData<AccountId>,143}144145/// The current version of Scheduled struct.146pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> = ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId>;147148// A value placed in storage that represents the current version of the Scheduler storage.149// This value is used by the `on_runtime_upgrade` logic to determine whether we run150// storage migration logic.151#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]152enum Releases {153 V1,154 V2,155}156157impl Default for Releases {158 fn default() -> Self {159 Releases::V1160 }161}162163#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]164pub struct CallSpec {165166 module: u32,167 method: u32,168}169170decl_storage! {171 trait Store for Module<T: Config> as Scheduler {172 /// Items to be executed, indexed by the block number that they should be executed on.173 pub Agenda: map hasher(twox_64_concat) T::BlockNumber174 => Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>;175176 pub SpecAgenda: map hasher(twox_64_concat) T::BlockNumber177 => Vec<Option<CallSpec>>; 178179 /// Lookup from identity to the block number and index of the task.180 Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;181182 /// Storage version of the pallet.183 ///184 /// New networks start with last version.185 StorageVersion build(|_| Releases::V2): Releases;186 }187}188189decl_event!(190 pub enum Event<T> where <T as system::Config>::BlockNumber {191 /// Scheduled some task. \[when, index\]192 Scheduled(BlockNumber, u32),193 /// Canceled some task. \[when, index\]194 Canceled(BlockNumber, u32),195 /// Dispatched some task. \[task, id, result\]196 Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),197 }198);199200decl_error! {201 pub enum Error for Module<T: Config> {202 /// Failed to schedule a call203 FailedToSchedule,204 /// Cannot find the scheduled call.205 NotFound,206 /// Given target block number is in the past.207 TargetBlockNumberInPast,208 /// Reschedule failed because it does not change scheduled time.209 RescheduleNoChange,210 }211}212213decl_module! {214 /// Scheduler module declaration.215 pub struct Module<T: Config> for enum Call 216 where 217 origin: <T as system::Config>::Origin218 {219 type Error = Error<T>;220 fn deposit_event() = default;221222223 /// Anonymously schedule a task.224 ///225 /// # <weight>226 /// - S = Number of already scheduled calls227 /// - Base Weight: 22.29 + .126 * S µs228 /// - DB Weight:229 /// - Read: Agenda230 /// - Write: Agenda231 /// - Will use base weight of 25 which should be good for up to 30 scheduled calls232 /// # </weight>233 // #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]234 #[weight = 0]235 fn schedule(origin,236 when: T::BlockNumber,237 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,238 priority: schedule::Priority,239 call: Box<<T as Config>::Call>,240 ) 241 {242 let origin = <T as Config>::Origin::from(origin);243 Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?;244 }245246 /// Cancel an anonymously scheduled task.247 ///248 /// # <weight>249 /// - S = Number of already scheduled calls250 /// - Base Weight: 22.15 + 2.869 * S µs251 /// - DB Weight:252 /// - Read: Agenda253 /// - Write: Agenda, Lookup254 /// - Will use base weight of 100 which should be good for up to 30 scheduled calls255 /// # </weight>256 #[weight = <T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get())]257 fn cancel(origin, when: T::BlockNumber, index: u32) {258 T::ScheduleOrigin::ensure_origin(origin.clone())?;259 let origin = <T as Config>::Origin::from(origin);260 Self::do_cancel(Some(origin.caller().clone()), (when, index))?;261 }262263 /// Schedule a named task.264 ///265 /// # <weight>266 /// - S = Number of already scheduled calls267 /// - Base Weight: 29.6 + .159 * S µs268 /// - DB Weight:269 /// - Read: Agenda, Lookup270 /// - Write: Agenda, Lookup271 /// - Will use base weight of 35 which should be good for more than 30 scheduled calls272 /// # </weight>273 #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]274 fn schedule_named(origin,275 id: Vec<u8>,276 when: T::BlockNumber,277 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,278 priority: schedule::Priority,279 call: Box<<T as Config>::Call>,280 ) {281 T::ScheduleOrigin::ensure_origin(origin.clone())?;282 let origin = <T as Config>::Origin::from(origin);283 Self::do_schedule_named(284 id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call285 )?;286 }287288 /// Cancel a named scheduled task.289 ///290 /// # <weight>291 /// - S = Number of already scheduled calls292 /// - Base Weight: 24.91 + 2.907 * S µs293 /// - DB Weight:294 /// - Read: Agenda, Lookup295 /// - Write: Agenda, Lookup296 /// - Will use base weight of 100 which should be good for up to 30 scheduled calls297 /// # </weight>298 #[weight = <T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())]299 fn cancel_named(origin, id: Vec<u8>) {300 T::ScheduleOrigin::ensure_origin(origin.clone())?;301 let origin = <T as Config>::Origin::from(origin);302 Self::do_cancel_named(Some(origin.caller().clone()), id)?;303 }304305 /// Anonymously schedule a task after a delay.306 ///307 /// # <weight>308 /// Same as [`schedule`].309 /// # </weight>310 #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]311 fn schedule_after(origin,312 after: T::BlockNumber,313 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,314 priority: schedule::Priority,315 call: Box<<T as Config>::Call>,316 ) {317 T::ScheduleOrigin::ensure_origin(origin.clone())?;318 let origin = <T as Config>::Origin::from(origin);319 Self::do_schedule(320 DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call321 )?;322 }323324 /// Schedule a named task after a delay.325 ///326 /// # <weight>327 /// Same as [`schedule_named`].328 /// # </weight>329 #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]330 fn schedule_named_after(origin,331 id: Vec<u8>,332 after: T::BlockNumber,333 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,334 priority: schedule::Priority,335 call: Box<<T as Config>::Call>,336 ) {337 T::ScheduleOrigin::ensure_origin(origin.clone())?;338 let origin = <T as Config>::Origin::from(origin);339 Self::do_schedule_named(340 id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call341 )?;342 }343344 /// Execute the scheduled calls345 ///346 /// # <weight>347 /// - S = Number of already scheduled calls348 /// - N = Named scheduled calls349 /// - P = Periodic Calls350 /// - Base Weight: 9.243 + 23.45 * S µs351 /// - DB Weight:352 /// - Read: Agenda + Lookup * N + Agenda(Future) * P353 /// - Write: Agenda + Lookup * N + Agenda(future) * P354 /// # </weight>355 fn on_initialize(now: T::BlockNumber) -> Weight {356 let limit = T::MaximumWeight::get();357 let mut queued = Agenda::<T>::take(now).into_iter()358 .enumerate()359 .filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))360 .collect::<Vec<_>>();361 if queued.len() as u32 > T::MaxScheduledPerBlock::get() {362 log::warn!(363 target: "runtime::scheduler",364 "Warning: This block has more items queued in Scheduler than \365 expected from the runtime configuration. An update might be needed."366 );367 }368 queued.sort_by_key(|(_, s)| s.priority);369 let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)370 let mut total_weight: Weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get());371 queued.into_iter()372 .enumerate()373 .scan(base_weight, |cumulative_weight, (order, (index, s))| {374 *cumulative_weight = cumulative_weight375 .saturating_add(s.call.get_dispatch_info().weight);376377 let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(378 s.origin.clone()379 ).into();380381 if ensure_signed(origin).is_ok() {382 // AccountData for inner call origin accountdata.383 *cumulative_weight = cumulative_weight384 .saturating_add(T::DbWeight::get().reads_writes(1, 1));385 }386387 if s.maybe_id.is_some() {388 // Remove/Modify Lookup389 *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1));390 }391 if s.maybe_periodic.is_some() {392 // Read/Write Agenda for future block393 *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));394 }395396 Some((order, index, *cumulative_weight, s))397 })398 .filter_map(|(order, index, cumulative_weight, mut s)| {399 // We allow a scheduled call if any is true:400 // - It's priority is `HARD_DEADLINE`401 // - It does not push the weight past the limit.402 // - It is the first item in the schedule403 if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 {404405 let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(406 s.origin.clone()407 ).into();408 let sender = ensure_signed(origin).unwrap_or(T::AccountId::default());409 let who_will_pay = T::Sponsoring::resolve(&sender, &s.call.clone()).unwrap_or(410 T::AccountId::default());411 let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));412 let r = s.call.clone().dispatch(sponsor.into());413 let maybe_id = s.maybe_id.clone();414 if let &Some((period, count)) = &s.maybe_periodic {415 if count > 1 {416 s.maybe_periodic = Some((period, count - 1));417 } else {418 s.maybe_periodic = None;419 }420 let next = now + period;421 // If scheduled is named, place it's information in `Lookup`422 if let Some(ref id) = s.maybe_id {423 let next_index = Agenda::<T>::decode_len(now + period).unwrap_or(0);424 Lookup::<T>::insert(id, (next, next_index as u32));425 }426 Agenda::<T>::append(next, Some(s));427 } else {428 if let Some(ref id) = s.maybe_id {429 Lookup::<T>::remove(id);430 }431 }432 Self::deposit_event(RawEvent::Dispatched(433 (now, index),434 maybe_id,435 r.map(|_| ()).map_err(|e| e.error)436 ));437 total_weight = cumulative_weight;438 None439 } else {440 Some(Some(s))441 }442 })443 .for_each(|unused| {444 let next = now + One::one();445 Agenda::<T>::append(next, unused);446 });447448 total_weight449 }450 }451}452453impl<T: Config> Module<T> {454 /// Migrate storage format from V1 to V2.455 /// Return true if migration is performed.456 pub fn migrate_v1_to_t2() -> bool {457 if StorageVersion::get() == Releases::V1 {458 StorageVersion::put(Releases::V2);459460 Agenda::<T>::translate::<461 Vec<Option<ScheduledV1<<T as Config>::Call, T::BlockNumber>>>, _462 >(|_, agenda| Some(463 agenda464 .into_iter()465 .map(|schedule| schedule.map(|schedule| ScheduledV2 {466 maybe_id: schedule.maybe_id,467 priority: schedule.priority,468 call: schedule.call,469 maybe_periodic: schedule.maybe_periodic,470 origin: system::RawOrigin::Root.into(),471 _phantom: Default::default(),472 }))473 .collect::<Vec<_>>()474 ));475476 true477 } else {478 false479 }480 }481482 /// Helper to migrate scheduler when the pallet origin type has changed.483 pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {484 Agenda::<T>::translate::<485 Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _486 >(|_, agenda| Some(487 agenda488 .into_iter()489 .map(|schedule| schedule.map(|schedule| Scheduled {490 maybe_id: schedule.maybe_id,491 priority: schedule.priority,492 call: schedule.call,493 maybe_periodic: schedule.maybe_periodic,494 origin: schedule.origin.into(),495 _phantom: Default::default(),496 }))497 .collect::<Vec<_>>()498 ));499 }500501 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {502 let now = frame_system::Pallet::<T>::block_number();503504 let when = match when {505 DispatchTime::At(x) => x,506 // The current block has already completed it's scheduled tasks, so507 // Schedule the task at lest one block after this current block.508 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one())509 };510511 if when <= now {512 return Err(Error::<T>::TargetBlockNumberInPast.into())513 }514515 Ok(when)516 }517518 fn do_schedule(519 when: DispatchTime<T::BlockNumber>,520 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,521 priority: schedule::Priority,522 origin: T::PalletsOrigin,523 call: <T as Config>::Call524 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {525 let when = Self::resolve_time(when)?;526527 // sanitize maybe_periodic528 let maybe_periodic = maybe_periodic529 .filter(|p| p.1 > 1 && !p.0.is_zero())530 // Remove one from the number of repetitions since we will schedule one now.531 .map(|(p, c)| (p, c - 1));532 let s = Some(Scheduled {533 maybe_id: None, priority, call, maybe_periodic, origin, _phantom: PhantomData::<T::AccountId>::default(),534 });535 Agenda::<T>::append(when, s);536 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;537 if index > T::MaxScheduledPerBlock::get() {538 log::warn!(539 target: "runtime::scheduler",540 "Warning: There are more items queued in the Scheduler than \541 expected from the runtime configuration. An update might be needed.",542 );543 }544 Self::deposit_event(RawEvent::Scheduled(when, index));545546 Ok((when, index))547 }548549 fn do_cancel(550 origin: Option<T::PalletsOrigin>,551 (when, index): TaskAddress<T::BlockNumber>552 ) -> Result<(), DispatchError> {553 let scheduled = Agenda::<T>::try_mutate(554 when,555 |agenda| {556 agenda.get_mut(index as usize)557 .map_or(Ok(None), |s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {558 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {559 if *o != s.origin {560 return Err(BadOrigin.into());561 }562 };563 Ok(s.take())564 })565 },566 )?;567 if let Some(s) = scheduled {568 if let Some(id) = s.maybe_id {569 Lookup::<T>::remove(id);570 }571 Self::deposit_event(RawEvent::Canceled(when, index));572 Ok(())573 } else {574 Err(Error::<T>::NotFound)?575 }576 }577578 fn do_reschedule(579 (when, index): TaskAddress<T::BlockNumber>,580 new_time: DispatchTime<T::BlockNumber>,581 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {582 let new_time = Self::resolve_time(new_time)?;583584 if new_time == when {585 return Err(Error::<T>::RescheduleNoChange.into());586 }587588 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {589 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;590 let task = task.take().ok_or(Error::<T>::NotFound)?;591 Agenda::<T>::append(new_time, Some(task));592 Ok(())593 })?;594595 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;596 Self::deposit_event(RawEvent::Canceled(when, index));597 Self::deposit_event(RawEvent::Scheduled(new_time, new_index));598599 Ok((new_time, new_index))600 }601602 fn do_schedule_named(603 id: Vec<u8>,604 when: DispatchTime<T::BlockNumber>,605 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,606 priority: schedule::Priority,607 origin: T::PalletsOrigin,608 call: <T as Config>::Call,609 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {610 // ensure id it is unique611 if Lookup::<T>::contains_key(&id) {612 return Err(Error::<T>::FailedToSchedule)?613 }614615 let when = Self::resolve_time(when)?;616617 // sanitize maybe_periodic618 let maybe_periodic = maybe_periodic619 .filter(|p| p.1 > 1 && !p.0.is_zero())620 // Remove one from the number of repetitions since we will schedule one now.621 .map(|(p, c)| (p, c - 1));622623 let s = Scheduled {624 maybe_id: Some(id.clone()), priority, call, maybe_periodic, origin, _phantom: Default::default()625 };626 Agenda::<T>::append(when, Some(s));627 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;628 if index > T::MaxScheduledPerBlock::get() {629 log::warn!(630 target: "runtime::scheduler",631 "Warning: There are more items queued in the Scheduler than \632 expected from the runtime configuration. An update might be needed.",633 );634 }635 let address = (when, index);636 Lookup::<T>::insert(&id, &address);637 Self::deposit_event(RawEvent::Scheduled(when, index));638639 Ok(address)640 }641642 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: Vec<u8>) -> DispatchResult {643 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {644 if let Some((when, index)) = lookup.take() {645 let i = index as usize;646 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {647 if let Some(s) = agenda.get_mut(i) {648 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {649 if *o != s.origin {650 return Err(BadOrigin.into());651 }652 }653 *s = None;654 }655 Ok(())656 })?;657 Self::deposit_event(RawEvent::Canceled(when, index));658 Ok(())659 } else {660 Err(Error::<T>::NotFound)?661 }662 })663 }664665 fn do_reschedule_named(666 id: Vec<u8>,667 new_time: DispatchTime<T::BlockNumber>,668 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {669 let new_time = Self::resolve_time(new_time)?;670671 Lookup::<T>::try_mutate_exists(id, |lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {672 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;673674 if new_time == when {675 return Err(Error::<T>::RescheduleNoChange.into());676 }677678 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {679 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;680 let task = task.take().ok_or(Error::<T>::NotFound)?;681 Agenda::<T>::append(new_time, Some(task));682683 Ok(())684 })?;685686 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;687 Self::deposit_event(RawEvent::Canceled(when, index));688 Self::deposit_event(RawEvent::Scheduled(new_time, new_index));689690 *lookup = Some((new_time, new_index));691692 Ok((new_time, new_index))693 })694 }695}696697impl<T: Config> schedule::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin> for Module<T> {698 type Address = TaskAddress<T::BlockNumber>;699700 fn schedule(701 when: DispatchTime<T::BlockNumber>,702 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,703 priority: schedule::Priority,704 origin: T::PalletsOrigin,705 call: <T as Config>::Call706 ) -> Result<Self::Address, DispatchError> {707 Self::do_schedule(when, maybe_periodic, priority, origin, call)708 }709710 fn cancel((when, index): Self::Address) -> Result<(), ()> {711 Self::do_cancel(None, (when, index)).map_err(|_| ())712 }713714 fn reschedule(715 address: Self::Address,716 when: DispatchTime<T::BlockNumber>,717 ) -> Result<Self::Address, DispatchError> {718 Self::do_reschedule(address, when)719 }720721 fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {722 Agenda::<T>::get(when).get(index as usize).ok_or(()).map(|_| when)723 }724}725726impl<T: Config> schedule::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin> for Module<T> {727 type Address = TaskAddress<T::BlockNumber>;728729 fn schedule_named(730 id: Vec<u8>,731 when: DispatchTime<T::BlockNumber>,732 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,733 priority: schedule::Priority,734 origin: T::PalletsOrigin,735 call: <T as Config>::Call,736 ) -> Result<Self::Address, ()> {737 Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ())738 }739740 fn cancel_named(id: Vec<u8>) -> Result<(), ()> {741 Self::do_cancel_named(None, id).map_err(|_| ())742 }743744 fn reschedule_named(745 id: Vec<u8>,746 when: DispatchTime<T::BlockNumber>,747 ) -> Result<Self::Address, DispatchError> {748 Self::do_reschedule_named(id, when)749 }750751 fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {752 Lookup::<T>::get(id).and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when)).ok_or(())753 }754}755756#[cfg(test)]757mod tests {758 use super::*;759760 use frame_support::{761 parameter_types, assert_ok, ord_parameter_types,762 assert_noop, assert_err, Hashable,763 traits::{OnInitialize, OnFinalize, Filter},764 weights::constants::RocksDbWeight,765 };766 use sp_core::H256;767 use sp_runtime::{768 Perbill,769 testing::Header,770 traits::{BlakeTwo256, IdentityLookup},771 };772 use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy};773 use substrate_test_utils::assert_eq_uvec;774 use crate as scheduler;775776 mod logger {777 use super::*;778 use std::cell::RefCell;779780 thread_local! {781 static LOG: RefCell<Vec<(OriginCaller, u32)>> = RefCell::new(Vec::new());782 }783 pub fn log() -> Vec<(OriginCaller, u32)> {784 LOG.with(|log| log.borrow().clone())785 }786 pub trait Config: system::Config {787 type Event: From<Event> + Into<<Self as system::Config>::Event>;788 }789 decl_event! {790 pub enum Event {791 Logged(u32, Weight),792 }793 }794 decl_module! {795 pub struct Module<T: Config> for enum Call796 where797 origin: <T as system::Config>::Origin,798 <T as system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>799 {800 fn deposit_event() = default;801802 #[weight = *weight]803 fn log(origin, i: u32, weight: Weight) {804 Self::deposit_event(Event::Logged(i, weight));805 LOG.with(|log| {806 log.borrow_mut().push((origin.caller().clone(), i));807 })808 }809810 #[weight = *weight]811 fn log_without_filter(origin, i: u32, weight: Weight) {812 Self::deposit_event(Event::Logged(i, weight));813 LOG.with(|log| {814 log.borrow_mut().push((origin.caller().clone(), i));815 })816 }817 }818 }819 }820821 type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;822 type Block = frame_system::mocking::MockBlock<Test>;823824 frame_support::construct_runtime!(825 pub enum Test where826 Block = Block,827 NodeBlock = Block,828 UncheckedExtrinsic = UncheckedExtrinsic,829 {830 System: frame_system::{Pallet, Call, Config, Storage, Event<T>},831 Logger: logger::{Pallet, Call, Event},832 Scheduler: scheduler::{Pallet, Call, Storage, Event<T>},833 }834 );835836 // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used.837 pub struct BaseFilter;838 impl Filter<Call> for BaseFilter {839 fn filter(call: &Call) -> bool {840 !matches!(call, Call::Logger(logger::Call::log(_, _)))841 }842 }843844 parameter_types! {845 pub const BlockHashCount: u64 = 250;846 pub BlockWeights: frame_system::limits::BlockWeights =847 frame_system::limits::BlockWeights::simple_max(2_000_000_000_000);848 }849 impl system::Config for Test {850 type BaseCallFilter = BaseFilter;851 type BlockWeights = ();852 type BlockLength = ();853 type DbWeight = RocksDbWeight;854 type Origin = Origin;855 type Call = Call;856 type Index = u64;857 type BlockNumber = u64;858 type Hash = H256;859 type Hashing = BlakeTwo256;860 type AccountId = u64;861 type Lookup = IdentityLookup<Self::AccountId>;862 type Header = Header;863 type Event = Event;864 type BlockHashCount = BlockHashCount;865 type Version = ();866 type PalletInfo = PalletInfo;867 type AccountData = ();868 type OnNewAccount = ();869 type OnKilledAccount = ();870 type SystemWeightInfo = ();871 type SS58Prefix = ();872 type OnSetCode = ();873 }874 impl logger::Config for Test {875 type Event = Event;876 }877 parameter_types! {878 pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;879 pub const MaxScheduledPerBlock: u32 = 10;880 }881 ord_parameter_types! {882 pub const One: u64 = 1;883 }884885 impl Config for Test {886 type Event = Event;887 type Origin = Origin;888 type PalletsOrigin = OriginCaller;889 type Call = Call;890 type MaximumWeight = MaximumSchedulerWeight;891 type ScheduleOrigin = EnsureOneOf<u64, EnsureRoot<u64>, EnsureSignedBy<One, u64>>;892 type MaxScheduledPerBlock = MaxScheduledPerBlock;893 type WeightInfo = ();894 }895896 pub fn new_test_ext() -> sp_io::TestExternalities {897 let t = system::GenesisConfig::default().build_storage::<Test>().unwrap();898 t.into()899 }900901 fn run_to_block(n: u64) {902 while System::block_number() < n {903 Scheduler::on_finalize(System::block_number());904 System::set_block_number(System::block_number() + 1);905 Scheduler::on_initialize(System::block_number());906 }907 }908909 fn root() -> OriginCaller {910 system::RawOrigin::Root.into()911 }912913 #[test]914 fn basic_scheduling_works() {915 new_test_ext().execute_with(|| {916 let call = Call::Logger(logger::Call::log(42, 1000));917 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));918 assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call));919 run_to_block(3);920 assert!(logger::log().is_empty());921 run_to_block(4);922 assert_eq!(logger::log(), vec![(root(), 42u32)]);923 run_to_block(100);924 assert_eq!(logger::log(), vec![(root(), 42u32)]);925 });926 }927928 #[test]929 fn schedule_after_works() {930 new_test_ext().execute_with(|| {931 run_to_block(2);932 let call = Call::Logger(logger::Call::log(42, 1000));933 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));934 // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6935 assert_ok!(Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call));936 run_to_block(5);937 assert!(logger::log().is_empty());938 run_to_block(6);939 assert_eq!(logger::log(), vec![(root(), 42u32)]);940 run_to_block(100);941 assert_eq!(logger::log(), vec![(root(), 42u32)]);942 });943 }944945 #[test]946 fn schedule_after_zero_works() {947 new_test_ext().execute_with(|| {948 run_to_block(2);949 let call = Call::Logger(logger::Call::log(42, 1000));950 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));951 assert_ok!(Scheduler::do_schedule(DispatchTime::After(0), None, 127, root(), call));952 // Will trigger on the next block.953 run_to_block(3);954 assert_eq!(logger::log(), vec![(root(), 42u32)]);955 run_to_block(100);956 assert_eq!(logger::log(), vec![(root(), 42u32)]);957 });958 }959960 #[test]961 fn periodic_scheduling_works() {962 new_test_ext().execute_with(|| {963 // at #4, every 3 blocks, 3 times.964 assert_ok!(Scheduler::do_schedule(965 DispatchTime::At(4), Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000))966 ));967 run_to_block(3);968 assert!(logger::log().is_empty());969 run_to_block(4);970 assert_eq!(logger::log(), vec![(root(), 42u32)]);971 run_to_block(6);972 assert_eq!(logger::log(), vec![(root(), 42u32)]);973 run_to_block(7);974 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);975 run_to_block(9);976 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);977 run_to_block(10);978 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);979 run_to_block(100);980 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);981 });982 }983984 #[test]985 fn reschedule_works() {986 new_test_ext().execute_with(|| {987 let call = Call::Logger(logger::Call::log(42, 1000));988 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));989 assert_eq!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), (4, 0));990991 run_to_block(3);992 assert!(logger::log().is_empty());993994 assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0));995996 assert_noop!(Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), Error::<Test>::RescheduleNoChange);997998 run_to_block(4);999 assert!(logger::log().is_empty());10001001 run_to_block(6);1002 assert_eq!(logger::log(), vec![(root(), 42u32)]);10031004 run_to_block(100);1005 assert_eq!(logger::log(), vec![(root(), 42u32)]);1006 });1007 }10081009 #[test]1010 fn reschedule_named_works() {1011 new_test_ext().execute_with(|| {1012 let call = Call::Logger(logger::Call::log(42, 1000));1013 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));1014 assert_eq!(Scheduler::do_schedule_named(1015 1u32.encode(), DispatchTime::At(4), None, 127, root(), call1016 ).unwrap(), (4, 0));10171018 run_to_block(3);1019 assert!(logger::log().is_empty());10201021 assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0));10221023 assert_noop!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), Error::<Test>::RescheduleNoChange);10241025 run_to_block(4);1026 assert!(logger::log().is_empty());10271028 run_to_block(6);1029 assert_eq!(logger::log(), vec![(root(), 42u32)]);10301031 run_to_block(100);1032 assert_eq!(logger::log(), vec![(root(), 42u32)]);1033 });1034 }10351036 #[test]1037 fn reschedule_named_perodic_works() {1038 new_test_ext().execute_with(|| {1039 let call = Call::Logger(logger::Call::log(42, 1000));1040 assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));1041 assert_eq!(Scheduler::do_schedule_named(1042 1u32.encode(), DispatchTime::At(4), Some((3, 3)), 127, root(), call1043 ).unwrap(), (4, 0));10441045 run_to_block(3);1046 assert!(logger::log().is_empty());10471048 assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), (5, 0));1049 assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0));10501051 run_to_block(5);1052 assert!(logger::log().is_empty());10531054 run_to_block(6);1055 assert_eq!(logger::log(), vec![(root(), 42u32)]);10561057 assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), (10, 0));10581059 run_to_block(9);1060 assert_eq!(logger::log(), vec![(root(), 42u32)]);10611062 run_to_block(10);1063 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);10641065 run_to_block(13);1066 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);10671068 run_to_block(100);1069 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);1070 });1071 }10721073 #[test]1074 fn cancel_named_scheduling_works_with_normal_cancel() {1075 new_test_ext().execute_with(|| {1076 // at #4.1077 Scheduler::do_schedule_named(1078 1u32.encode(), DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(69, 1000))1079 ).unwrap();1080 let i = Scheduler::do_schedule(1081 DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(42, 1000))1082 ).unwrap();1083 run_to_block(3);1084 assert!(logger::log().is_empty());1085 assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));1086 assert_ok!(Scheduler::do_cancel(None, i));1087 run_to_block(100);1088 assert!(logger::log().is_empty());1089 });1090 }10911092 #[test]1093 fn cancel_named_periodic_scheduling_works() {1094 new_test_ext().execute_with(|| {1095 // at #4, every 3 blocks, 3 times.1096 Scheduler::do_schedule_named(1097 1u32.encode(),1098 DispatchTime::At(4),1099 Some((3, 3)),1100 127,1101 root(),1102 Call::Logger(logger::Call::log(42, 1000))1103 ).unwrap();1104 // same id results in error.1105 assert!(Scheduler::do_schedule_named(1106 1u32.encode(),1107 DispatchTime::At(4),1108 None,1109 127,1110 root(),1111 Call::Logger(logger::Call::log(69, 1000))1112 ).is_err());1113 // different id is ok.1114 Scheduler::do_schedule_named(1115 2u32.encode(), DispatchTime::At(8), None, 127, root(), Call::Logger(logger::Call::log(69, 1000))1116 ).unwrap();1117 run_to_block(3);1118 assert!(logger::log().is_empty());1119 run_to_block(4);1120 assert_eq!(logger::log(), vec![(root(), 42u32)]);1121 run_to_block(6);1122 assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));1123 run_to_block(100);1124 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);1125 });1126 }11271128 #[test]1129 fn scheduler_respects_weight_limits() {1130 new_test_ext().execute_with(|| {1131 assert_ok!(Scheduler::do_schedule(1132 DispatchTime::At(4),1133 None,1134 127,1135 root(),1136 Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))1137 ));1138 assert_ok!(Scheduler::do_schedule(1139 DispatchTime::At(4),1140 None,1141 127,1142 root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))1143 ));1144 // 69 and 42 do not fit together1145 run_to_block(4);1146 assert_eq!(logger::log(), vec![(root(), 42u32)]);1147 run_to_block(5);1148 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);1149 });1150 }11511152 #[test]1153 fn scheduler_respects_hard_deadlines_more() {1154 new_test_ext().execute_with(|| {1155 assert_ok!(Scheduler::do_schedule(1156 DispatchTime::At(4),1157 None,1158 0,1159 root(),1160 Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))1161 ));1162 assert_ok!(Scheduler::do_schedule(1163 DispatchTime::At(4),1164 None,1165 0,1166 root(),1167 Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))1168 ));1169 // With base weights, 69 and 42 should not fit together, but do because of hard deadlines1170 run_to_block(4);1171 assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);1172 });1173 }11741175 #[test]1176 fn scheduler_respects_priority_ordering() {1177 new_test_ext().execute_with(|| {1178 assert_ok!(Scheduler::do_schedule(1179 DispatchTime::At(4),1180 None,1181 1,1182 root(),1183 Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))1184 ));1185 assert_ok!(Scheduler::do_schedule(1186 DispatchTime::At(4),1187 None,1188 0,1189 root(),1190 Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))1191 ));1192 run_to_block(4);1193 assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]);1194 });1195 }11961197 #[test]1198 fn scheduler_respects_priority_ordering_with_soft_deadlines() {1199 new_test_ext().execute_with(|| {1200 assert_ok!(Scheduler::do_schedule(1201 DispatchTime::At(4),1202 None,1203 255,1204 root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))1205 ));1206 assert_ok!(Scheduler::do_schedule(1207 DispatchTime::At(4),1208 None,1209 127,1210 root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))1211 ));1212 assert_ok!(Scheduler::do_schedule(1213 DispatchTime::At(4),1214 None,1215 126,1216 root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))1217 ));12181219 // 2600 does not fit with 69 or 42, but has higher priority, so will go through1220 run_to_block(4);1221 assert_eq!(logger::log(), vec![(root(), 2600u32)]);1222 // 69 and 42 fit together1223 run_to_block(5);1224 assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);1225 });1226 }12271228 #[test]1229 fn on_initialize_weight_is_correct() {1230 new_test_ext().execute_with(|| {1231 let base_weight: Weight = <Test as frame_system::Config>::DbWeight::get().reads_writes(1, 2);1232 let base_multiplier = 0;1233 let named_multiplier = <Test as frame_system::Config>::DbWeight::get().writes(1);1234 let periodic_multiplier = <Test as frame_system::Config>::DbWeight::get().reads_writes(1, 1);12351236 // Named1237 assert_ok!(1238 Scheduler::do_schedule_named(1239 1u32.encode(), DispatchTime::At(1), None, 255, root(),1240 Call::Logger(logger::Call::log(3, MaximumSchedulerWeight::get() / 3))1241 )1242 );1243 // Anon Periodic1244 assert_ok!(Scheduler::do_schedule(1245 DispatchTime::At(1),1246 Some((1000, 3)),1247 128,1248 root(),1249 Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))1250 ));1251 // Anon1252 assert_ok!(Scheduler::do_schedule(1253 DispatchTime::At(1),1254 None,1255 127,1256 root(),1257 Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))1258 ));1259 // Named Periodic1260 assert_ok!(Scheduler::do_schedule_named(1261 2u32.encode(), DispatchTime::At(1), Some((1000, 3)), 126, root(),1262 Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2)))1263 );12641265 // Will include the named periodic only1266 let actual_weight = Scheduler::on_initialize(1);1267 let call_weight = MaximumSchedulerWeight::get() / 2;1268 assert_eq!(1269 actual_weight, call_weight + base_weight + base_multiplier + named_multiplier + periodic_multiplier1270 );1271 assert_eq!(logger::log(), vec![(root(), 2600u32)]);12721273 // Will include anon and anon periodic1274 let actual_weight = Scheduler::on_initialize(2);1275 let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3;1276 assert_eq!(actual_weight, call_weight + base_weight + base_multiplier * 2 + periodic_multiplier);1277 assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);12781279 // Will include named only1280 let actual_weight = Scheduler::on_initialize(3);1281 let call_weight = MaximumSchedulerWeight::get() / 3;1282 assert_eq!(actual_weight, call_weight + base_weight + base_multiplier + named_multiplier);1283 assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)]);12841285 // Will contain none1286 let actual_weight = Scheduler::on_initialize(4);1287 assert_eq!(actual_weight, 0);1288 });1289 }12901291 #[test]1292 fn root_calls_works() {1293 new_test_ext().execute_with(|| {1294 let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));1295 let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));1296 assert_ok!(Scheduler::schedule_named(Origin::root(), 1u32.encode(), 4, None, 127, call));1297 assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2));1298 run_to_block(3);1299 // Scheduled calls are in the agenda.1300 assert_eq!(Agenda::<Test>::get(4).len(), 2);1301 assert!(logger::log().is_empty());1302 assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode()));1303 assert_ok!(Scheduler::cancel(Origin::root(), 4, 1));1304 // Scheduled calls are made NONE, so should not effect state1305 run_to_block(100);1306 assert!(logger::log().is_empty());1307 });1308 }13091310 #[test]1311 fn fails_to_schedule_task_in_the_past() {1312 new_test_ext().execute_with(|| {1313 run_to_block(3);13141315 let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));1316 let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));13171318 assert_err!(1319 Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call),1320 Error::<Test>::TargetBlockNumberInPast,1321 );13221323 assert_err!(1324 Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()),1325 Error::<Test>::TargetBlockNumberInPast,1326 );13271328 assert_err!(1329 Scheduler::schedule(Origin::root(), 3, None, 127, call2),1330 Error::<Test>::TargetBlockNumberInPast,1331 );1332 });1333 }13341335 #[test]1336 fn should_use_orign() {1337 new_test_ext().execute_with(|| {1338 let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));1339 let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));1340 assert_ok!(1341 Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call)1342 );1343 assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2));1344 run_to_block(3);1345 // Scheduled calls are in the agenda.1346 assert_eq!(Agenda::<Test>::get(4).len(), 2);1347 assert!(logger::log().is_empty());1348 assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), 1u32.encode()));1349 assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1));1350 // Scheduled calls are made NONE, so should not effect state1351 run_to_block(100);1352 assert!(logger::log().is_empty());1353 });1354 }13551356 #[test]1357 fn should_check_orign() {1358 new_test_ext().execute_with(|| {1359 let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));1360 let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));1361 assert_noop!(1362 Scheduler::schedule_named(system::RawOrigin::Signed(2).into(), 1u32.encode(), 4, None, 127, call),1363 BadOrigin1364 );1365 assert_noop!(Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), BadOrigin);1366 });1367 }13681369 #[test]1370 fn should_check_orign_for_cancel() {1371 new_test_ext().execute_with(|| {1372 let call = Box::new(Call::Logger(logger::Call::log_without_filter(69, 1000)));1373 let call2 = Box::new(Call::Logger(logger::Call::log_without_filter(42, 1000)));1374 assert_ok!(1375 Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call)1376 );1377 assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2));1378 run_to_block(3);1379 // Scheduled calls are in the agenda.1380 assert_eq!(Agenda::<Test>::get(4).len(), 2);1381 assert!(logger::log().is_empty());1382 assert_noop!(Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), BadOrigin);1383 assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin);1384 assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), BadOrigin);1385 assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin);1386 run_to_block(5);1387 assert_eq!(1388 logger::log(),1389 vec![(system::RawOrigin::Signed(1).into(), 69u32), (system::RawOrigin::Signed(1).into(), 42u32)]1390 );1391 });1392 }13931394 #[test]1395 fn migration_to_v2_works() {1396 new_test_ext().execute_with(|| {1397 for i in 0..3u64 {1398 let k = i.twox_64_concat();1399 let old = vec![1400 Some(ScheduledV1 {1401 maybe_id: None,1402 priority: i as u8 + 10,1403 call: Call::Logger(logger::Call::log(96, 100)),1404 maybe_periodic: None,1405 }),1406 None,1407 Some(ScheduledV1 {1408 maybe_id: Some(b"test".to_vec()),1409 priority: 123,1410 call: Call::Logger(logger::Call::log(69, 1000)),1411 maybe_periodic: Some((456u64, 10)),1412 }),1413 ];1414 frame_support::migration::put_storage_value(1415 b"Scheduler",1416 b"Agenda",1417 &k,1418 old,1419 );1420 }14211422 assert_eq!(StorageVersion::get(), Releases::V1);14231424 assert!(Scheduler::migrate_v1_to_t2());14251426 assert_eq_uvec!(Agenda::<Test>::iter().collect::<Vec<_>>(), vec![1427 (1428 0,1429 vec![1430 Some(ScheduledV2 {1431 maybe_id: None,1432 priority: 10,1433 call: Call::Logger(logger::Call::log(96, 100)),1434 maybe_periodic: None,1435 origin: root(),1436 _phantom: PhantomData::<u64>::default(),1437 }),1438 None,1439 Some(ScheduledV2 {1440 maybe_id: Some(b"test".to_vec()),1441 priority: 123,1442 call: Call::Logger(logger::Call::log(69, 1000)),1443 maybe_periodic: Some((456u64, 10)),1444 origin: root(),1445 _phantom: PhantomData::<u64>::default(),1446 }),1447 ]),1448 (1449 1,1450 vec![1451 Some(ScheduledV2 {1452 maybe_id: None,1453 priority: 11,1454 call: Call::Logger(logger::Call::log(96, 100)),1455 maybe_periodic: None,1456 origin: root(),1457 _phantom: PhantomData::<u64>::default(),1458 }),1459 None,1460 Some(ScheduledV2 {1461 maybe_id: Some(b"test".to_vec()),1462 priority: 123,1463 call: Call::Logger(logger::Call::log(69, 1000)),1464 maybe_periodic: Some((456u64, 10)),1465 origin: root(),1466 _phantom: PhantomData::<u64>::default(),1467 }),1468 ]1469 ),1470 (1471 2,1472 vec![1473 Some(ScheduledV2 {1474 maybe_id: None,1475 priority: 12,1476 call: Call::Logger(logger::Call::log(96, 100)),1477 maybe_periodic: None,1478 origin: root(),1479 _phantom: PhantomData::<u64>::default(),1480 }),1481 None,1482 Some(ScheduledV2 {1483 maybe_id: Some(b"test".to_vec()),1484 priority: 123,1485 call: Call::Logger(logger::Call::log(69, 1000)),1486 maybe_periodic: Some((456u64, 10)),1487 origin: root(),1488 _phantom: PhantomData::<u64>::default(),1489 }),1490 ]1491 )1492 ]);14931494 assert_eq!(StorageVersion::get(), Releases::V2);1495 });1496 }14971498 #[test]1499 fn test_migrate_origin() {1500 new_test_ext().execute_with(|| {1501 for i in 0..3u64 {1502 let k = i.twox_64_concat();1503 let old: Vec<Option<Scheduled<_, _, u32, u64>>> = vec![1504 Some(Scheduled {1505 maybe_id: None,1506 priority: i as u8 + 10,1507 call: Call::Logger(logger::Call::log(96, 100)),1508 origin: 3u32,1509 maybe_periodic: None,1510 _phantom: Default::default(),1511 }),1512 None,1513 Some(Scheduled {1514 maybe_id: Some(b"test".to_vec()),1515 priority: 123,1516 origin: 2u32,1517 call: Call::Logger(logger::Call::log(69, 1000)),1518 maybe_periodic: Some((456u64, 10)),1519 _phantom: Default::default(),1520 }),1521 ];1522 frame_support::migration::put_storage_value(1523 b"Scheduler",1524 b"Agenda",1525 &k,1526 old,1527 );1528 }15291530 impl Into<OriginCaller> for u32 {1531 fn into(self) -> OriginCaller {1532 match self {1533 3u32 => system::RawOrigin::Root.into(),1534 2u32 => system::RawOrigin::None.into(),1535 _ => unreachable!("test make no use of it"),1536 }1537 }1538 }15391540 Scheduler::migrate_origin::<u32>();15411542 assert_eq_uvec!(Agenda::<Test>::iter().collect::<Vec<_>>(), vec![1543 (1544 0,1545 vec![1546 Some(ScheduledV2::<_, _, OriginCaller, u64> {1547 maybe_id: None,1548 priority: 10,1549 call: Call::Logger(logger::Call::log(96, 100)),1550 maybe_periodic: None,1551 origin: system::RawOrigin::Root.into(),1552 _phantom: PhantomData::<u64>::default(),1553 }),1554 None,1555 Some(ScheduledV2 {1556 maybe_id: Some(b"test".to_vec()),1557 priority: 123,1558 call: Call::Logger(logger::Call::log(69, 1000)),1559 maybe_periodic: Some((456u64, 10)),1560 origin: system::RawOrigin::None.into(),1561 _phantom: PhantomData::<u64>::default(),1562 }),1563 ]),1564 (1565 1,1566 vec![1567 Some(ScheduledV2 {1568 maybe_id: None,1569 priority: 11,1570 call: Call::Logger(logger::Call::log(96, 100)),1571 maybe_periodic: None,1572 origin: system::RawOrigin::Root.into(),1573 _phantom: PhantomData::<u64>::default(),1574 }),1575 None,1576 Some(ScheduledV2 {1577 maybe_id: Some(b"test".to_vec()),1578 priority: 123,1579 call: Call::Logger(logger::Call::log(69, 1000)),1580 maybe_periodic: Some((456u64, 10)),1581 origin: system::RawOrigin::None.into(),1582 _phantom: PhantomData::<u64>::default(),1583 }),1584 ]1585 ),1586 (1587 2,1588 vec![1589 Some(ScheduledV2 {1590 maybe_id: None,1591 priority: 12,1592 call: Call::Logger(logger::Call::log(96, 100)),1593 maybe_periodic: None,1594 origin: system::RawOrigin::Root.into(),1595 _phantom: PhantomData::<u64>::default(),1596 }),1597 None,1598 Some(ScheduledV2 {1599 maybe_id: Some(b"test".to_vec()),1600 priority: 123,1601 call: Call::Logger(logger::Call::log(69, 1000)),1602 maybe_periodic: Some((456u64, 10)),1603 origin: system::RawOrigin::None.into(),1604 _phantom: PhantomData::<u64>::default(),1605 }),1606 ]1607 )1608 ]);1609 });1610 }1611}tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -13,7 +13,8 @@
// Pallets that must always be present
const requiredPallets = [
- 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting'
+ 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting',
+ 'scheduler', 'nftpayment', 'charging'
];
// Pallets that depend on consensus and governance configuration
tests/src/scheduler.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/scheduler.test.ts
@@ -0,0 +1,94 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ createItemExpectSuccess,
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ findNotExistingCollection,
+ queryCollectionExpectSuccess,
+ setOffchainSchemaExpectFailure,
+ setOffchainSchemaExpectSuccess,
+ transferExpectSuccess,
+ scheduleTransferExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ confirmSponsorshipExpectSuccess
+} from './util/helpers';
+
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const DATA = [1, 2, 3, 4];
+
+describe('Integration Test scheduler base transaction', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ });
+ });
+
+ it('User can transfer owned token with delay (scheduler)', async () => {
+ await usingApi(async (api) => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(nftCollectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(nftCollectionId);
+
+ await scheduleTransferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1, 'NFT');
+ });
+ });
+
+
+});
+
+// describe('Negative Integration Test setOffchainSchema', () => {
+// let alice: IKeyringPair;
+// let bob: IKeyringPair;
+
+// let validCollectionId: number;
+
+// before(async () => {
+// await usingApi(async () => {
+// alice = privateKey('//Alice');
+// bob = privateKey('//Bob');
+
+// validCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// });
+// });
+
+// it('fails on not existing collection id', async () => {
+// const nonExistingCollectionId = await usingApi(findNotExistingCollection);
+
+// await setOffchainSchemaExpectFailure(alice, nonExistingCollectionId, DATA);
+// });
+
+// it('fails on destroyed collection id', async () => {
+// const destroyedCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// await destroyCollectionExpectSuccess(destroyedCollectionId);
+
+// await setOffchainSchemaExpectFailure(alice, destroyedCollectionId, DATA);
+// });
+
+// it('fails on too long data', async () => {
+// const tooLongData = new Array(4097).fill(0xff);
+
+// await setOffchainSchemaExpectFailure(alice, validCollectionId, tooLongData);
+// });
+
+// it('fails on execution by non-owner', async () => {
+// await setOffchainSchemaExpectFailure(bob, validCollectionId, DATA);
+// });
+// });
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -5,7 +5,7 @@
import { ApiPromise, Keyring } from '@polkadot/api';
import { Enum, Struct } from '@polkadot/types/codec';
-import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
+import type { AccountId, BlockNumber, Call, EventRecord } from '@polkadot/types/interfaces';
import { u128 } from '@polkadot/types/primitive';
import { IKeyringPair } from '@polkadot/types/types';
import { BigNumber } from 'bignumber.js';
@@ -17,6 +17,8 @@
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from '../substrate/substrate-api';
import { ICollectionInterface } from '../types';
import { hexToStr, strToUTF16, utf16ToStr } from './util';
+import { Compact, Option, Raw, Vec } from '@polkadot/types/codec';
+// import { AccountId, AccountIdOf, AccountIndex, Address, AssetId, Balance, BalanceOf, Block, BlockNumber, Call, ChangesTrieConfiguration, Consensus, ConsensusEngineId, Digest, DigestItem, DispatchClass, DispatchInfo, DispatchInfoTo190, EcdsaSignature, Ed25519Signature, Extrinsic, ExtrinsicEra, ExtrinsicPayload, ExtrinsicPayloadUnknown, ExtrinsicPayloadV1, ExtrinsicPayloadV2, ExtrinsicPayloadV3, ExtrinsicPayloadV4, ExtrinsicSignatureV1, ExtrinsicSignatureV2, ExtrinsicSignatureV3, ExtrinsicSignatureV4, ExtrinsicUnknown, ExtrinsicV1, ExtrinsicV2, ExtrinsicV3, ExtrinsicV4, Hash, Header, ImmortalEra, Index, Justification, KeyTypeId, KeyValue, LockIdentifier, LookupSource, LookupTarget, Moment, MortalEra, MultiSignature, Origin, Perbill, Percent, Permill, Perquintill, Phantom, PhantomData, PreRuntime, Seal, SealV0, Signature, SignedBlock, SignerPayload, Sr25519Signature, ValidatorId, Weight, WeightMultiplier } from '@polkadot/types/interfaces/runtime';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -702,6 +704,54 @@
});
}
+async function getBlockNumber(api: ApiPromise): Promise<number> {
+ return new Promise<number>(async (resolve, reject) => {
+ const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => {
+ unsubscribe();
+ resolve(head.number.toNumber());
+ });
+ });
+}
+
+export async function
+scheduleTransferExpectSuccess(collectionId: number,
+ tokenId: number,
+ sender: IKeyringPair,
+ recipient: IKeyringPair,
+ value: number | bigint = 1,
+ type: string = 'NFT') {
+ await usingApi(async (api: ApiPromise) => {
+ let balanceBefore = new BN(0);
+
+
+ let blockNumber: number | undefined = await getBlockNumber(api);
+ let expectedBlockNumber = blockNumber + 2;
+
+ expect(blockNumber).to.be.greaterThan(0);
+ const transferTx = await api.tx.nft.transfer(recipient.address, collectionId, tokenId, value);
+ const scheduleTx = await api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx);
+
+ const events = await submitTransactionAsync(sender, scheduleTx);
+
+ const sponsorBalanceBefore = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceBefore = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemDataBefore = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemDataBefore.Owner.toString()).to.be.equal(sender.address);
+
+ // sleep for 2 blocks
+ await new Promise(resolve => setTimeout(resolve, 6000 * 2));
+
+ const sponsorBalanceAfter = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceAfter = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemData = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemData.Owner.toString()).to.be.equal(recipient.address);
+ expect(recipientBalanceAfter.toNumber()).to.be.equal(recipientBalanceBefore.toNumber());
+ });
+}
+
+
export async function
transferExpectSuccess(collectionId: number,
tokenId: number,