difftreelog
feat api for recursive item burning
in: master
17 files changed
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -20,8 +20,8 @@
use frame_benchmarking::{benchmarks, account};
use up_data_structs::{
CollectionMode, CreateCollectionData, CollectionId, Property, PropertyKey, PropertyValue,
- MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
- OFFCHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, MAX_PROPERTIES_PER_ITEM,
+ CollectionPermissions, NestingRule, MAX_COLLECTION_NAME_LENGTH,
+ MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, MAX_PROPERTIES_PER_ITEM,
};
use frame_support::{
traits::{Currency, Get},
@@ -74,15 +74,15 @@
}
pub fn create_collection_raw<T: Config, R>(
- owner: T::AccountId,
+ owner: T::CrossAccountId,
mode: CollectionMode,
handler: impl FnOnce(
- T::AccountId,
+ T::CrossAccountId,
CreateCollectionData<T::AccountId>,
) -> Result<CollectionId, DispatchError>,
cast: impl FnOnce(CollectionHandle<T>) -> R,
) -> Result<R, DispatchError> {
- T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
+ <T as Config>::Currency::deposit_creating(&owner.as_sub(), T::CollectionCreationPrice::get());
let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
@@ -93,13 +93,19 @@
name,
description,
token_prefix,
+ permissions: Some(CollectionPermissions {
+ nesting: Some(NestingRule::Permissive),
+ ..Default::default()
+ }),
..Default::default()
},
)
.and_then(CollectionHandle::try_get)
.map(cast)
}
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<CollectionHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+ owner: T::CrossAccountId,
+) -> Result<CollectionHandle<T>, DispatchError> {
create_collection_raw(
owner,
CollectionMode::NFT,
@@ -127,7 +133,7 @@
bench_init!($($rest)*);
};
($name:ident: collection($owner:ident); $($rest:tt)*) => {
- let $name = create_collection::<T>($owner.clone())?;
+ let $name = create_collection::<T>(T::CrossAccountId::from_sub($owner.clone()))?;
bench_init!($($rest)*);
};
($name:ident: cross; $($rest:tt)*) => {
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1168,8 +1168,9 @@
);
Ok(new_limit)
}
+
pub fn clamp_permissions(
- mode: CollectionMode,
+ _mode: CollectionMode,
old_limit: &CollectionPermissions,
mut new_limit: CollectionPermissions,
) -> Result<CollectionPermissions, DispatchError> {
@@ -1204,6 +1205,22 @@
fn approve() -> Weight;
fn transfer_from() -> Weight;
fn burn_from() -> Weight;
+
+ /// Differs from burn_item in case of Fungible and Refungible, as it should burn
+ /// whole users's balance
+ ///
+ /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead
+ fn burn_recursively_self_raw() -> Weight;
+ /// Cost of iterating over `amount` children while burning, without counting child burning itself
+ ///
+ /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead
+ fn burn_recursively_breadth_raw(amount: u32) -> Weight;
+
+ fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {
+ Self::burn_recursively_self_raw()
+ .saturating_mul(max_selfs.max(1) as u64)
+ .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))
+ }
}
pub trait CommonCollectionOperations<T: Config> {
@@ -1233,6 +1250,13 @@
token: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo;
+ fn burn_item_recursively(
+ &self,
+ sender: T::CrossAccountId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo;
fn set_collection_properties(
&self,
sender: T::CrossAccountId,
pallets/fungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/fungible/src/benchmarking.rs
+++ b/pallets/fungible/src/benchmarking.rs
@@ -25,7 +25,9 @@
const SEED: u32 = 1;
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<FungibleHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+ owner: T::CrossAccountId,
+) -> Result<FungibleHandle<T>, DispatchError> {
create_collection_raw(
owner,
CollectionMode::Fungible(0),
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -16,9 +16,10 @@
use core::marker::PhantomData;
-use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
+use pallet_structure::Error as StructureError;
use sp_runtime::ArithmeticError;
use sp_std::{vec::Vec, vec};
use up_data_structs::{Property, PropertyKey, PropertyValue, PropertyKeyPermission};
@@ -91,6 +92,16 @@
fn burn_from() -> Weight {
<SelfWeightOf<T>>::burn_from()
}
+
+ fn burn_recursively_self_raw() -> Weight {
+ // Read to get total balance
+ Self::burn_item() + T::DbWeight::get().reads(1)
+ }
+
+ fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+ // Fungible tokens can't have children
+ 0
+ }
}
impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
@@ -170,6 +181,26 @@
)
}
+ fn burn_item_recursively(
+ &self,
+ sender: T::CrossAccountId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ _breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ // Should not happen?
+ ensure!(
+ token == TokenId::default(),
+ <Error<T>>::FungibleItemsHaveNoId
+ );
+ ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+
+ with_weight(
+ <Pallet<T>>::burn(self, &sender, <Balance<T>>::get((self.id, &sender))),
+ <CommonWeights<T>>::burn_recursively_self_raw(),
+ )
+ }
+
fn transfer(
&self,
from: T::CrossAccountId,
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,12 +18,9 @@
use crate::{Pallet, Config, NonfungibleHandle};
use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data, property_key, property_value};
+use pallet_common::benchmarking::{create_collection_raw, property_key, property_value};
use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{
- CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT,
- budget::Unlimited,
-};
+use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited};
use pallet_common::bench_init;
const SEED: u32 = 1;
@@ -49,7 +46,7 @@
}
fn create_collection<T: Config>(
- owner: T::AccountId,
+ owner: T::CrossAccountId,
) -> Result<NonfungibleHandle<T>, DispatchError> {
create_collection_raw(
owner,
@@ -96,6 +93,26 @@
let item = create_max_item(&collection, &sender, burner.clone())?;
}: {<Pallet<T>>::burn(&collection, &burner, item)?}
+ burn_recursively_self_raw {
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ sender: cross_from_sub(owner); burner: cross_sub;
+ };
+ let item = create_max_item(&collection, &sender, burner.clone())?;
+ }: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+
+ burn_recursively_breadth_plus_self_plus_self_per_each_raw {
+ let b in 0..200;
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ sender: cross_from_sub(owner); burner: cross_sub;
+ };
+ let item = create_max_item(&collection, &sender, burner.clone())?;
+ for i in 0..b {
+ create_max_item(&collection, &sender, T::CrossTokenAddressMapping::token_to_address(collection.id, item))?;
+ }
+ }: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+
transfer {
bench_init!{
owner: sub; collection: collection(owner);
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -108,6 +108,15 @@
fn burn_from() -> Weight {
<SelfWeightOf<T>>::burn_from()
}
+
+ fn burn_recursively_self_raw() -> Weight {
+ <SelfWeightOf<T>>::burn_recursively_self_raw()
+ }
+
+ fn burn_recursively_breadth_raw(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount)
+ .saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1))
+ }
}
fn map_create_data<T: Config>(
@@ -264,6 +273,16 @@
}
}
+ fn burn_item_recursively(
+ &self,
+ sender: T::CrossAccountId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ <Pallet<T>>::burn_recursively(self, &sender, token, self_budget, breadth_budget)
+ }
+
fn transfer(
&self,
from: T::CrossAccountId,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -18,7 +18,13 @@
use erc::ERC721Events;
use evm_coder::ToLog;
-use frame_support::{BoundedVec, ensure, fail, transactional, storage::with_transaction};
+use frame_support::{
+ BoundedVec, ensure, fail, transactional,
+ storage::with_transaction,
+ pallet_prelude::DispatchResultWithPostInfo,
+ pallet_prelude::Weight,
+ weights::{PostDispatchInfo, Pays},
+};
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
@@ -29,7 +35,7 @@
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
eth::collection_id_to_address,
};
-use pallet_structure::Pallet as PalletStructure;
+use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
@@ -39,6 +45,7 @@
use scale_info::TypeInfo;
pub use pallet::*;
+use weights::WeightInfo;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod common;
@@ -373,7 +380,7 @@
<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
collection.id,
token,
- sender.clone(),
+ token_data.owner.clone(),
old_spender,
0,
));
@@ -396,6 +403,45 @@
Ok(())
}
+ #[transactional]
+ pub fn burn_recursively(
+ collection: &NonfungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+
+ let current_token_account =
+ T::CrossTokenAddressMapping::token_to_address(collection.id, token);
+
+ let mut weight = 0 as Weight;
+
+ // This method is transactional, if user in fact doesn't have permissions to remove token -
+ // tokens removed here will be restored after rejected transaction
+ for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {
+ ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);
+ let PostDispatchInfo { actual_weight, .. } =
+ <PalletStructure<T>>::burn_item_recursively(
+ current_token_account.clone(),
+ collection,
+ token,
+ self_budget,
+ breadth_budget,
+ )?;
+ if let Some(actual_weight) = actual_weight {
+ weight = weight.saturating_add(actual_weight);
+ }
+ }
+
+ Self::burn(collection, sender, token)?;
+ DispatchResultWithPostInfo::Ok(PostDispatchInfo {
+ actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),
+ pays_fee: Pays::Yes,
+ })
+ }
+
pub fn set_token_property(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -964,6 +1010,7 @@
);
ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?
}
+ NestingRule::Permissive => {}
}
Ok(())
}
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -92,7 +92,35 @@
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
-
+ // Storage: Nonfungible TokenChildren (r:1 w:0)
+ // Storage: Nonfungible TokenData (r:1 w:1)
+ // Storage: Nonfungible TokensBurnt (r:1 w:1)
+ // Storage: Nonfungible AccountBalance (r:1 w:1)
+ // Storage: Nonfungible Allowance (r:1 w:0)
+ // Storage: Nonfungible Owned (r:0 w:1)
+ // Storage: Nonfungible TokenProperties (r:0 w:1)
+ fn burn_recursively_self_raw() -> Weight {
+ (86_136_000 as Weight)
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().writes(5 as Weight))
+ }
+ // Storage: Nonfungible TokenChildren (r:1 w:0)
+ // Storage: Nonfungible TokenData (r:1 w:1)
+ // Storage: Nonfungible TokensBurnt (r:1 w:1)
+ // Storage: Nonfungible AccountBalance (r:1 w:1)
+ // Storage: Nonfungible Allowance (r:1 w:0)
+ // Storage: Nonfungible Owned (r:0 w:1)
+ // Storage: Nonfungible TokenProperties (r:0 w:1)
+ // Storage: Common CollectionById (r:1 w:0)
+ fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 42_828_000
+ .saturating_add((381_478_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
+ .saturating_add(T::DbWeight::get().writes(5 as Weight))
+ .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
+ }
// Storage: Nonfungible TokenData (r:1 w:1)
// Storage: Nonfungible AccountBalance (r:2 w:2)
// Storage: Nonfungible Allowance (r:1 w:0)
pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -50,7 +50,9 @@
Ok(TokenId(<TokensMinted<T>>::get(&collection.id)))
}
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<RefungibleHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+ owner: T::CrossAccountId,
+) -> Result<RefungibleHandle<T>, DispatchError> {
create_collection_raw(
owner,
CollectionMode::NFT,
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -17,12 +17,13 @@
use core::marker::PhantomData;
use sp_std::collections::btree_map::BTreeMap;
-use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
use up_data_structs::{
CollectionId, TokenId, CreateItemExData, CreateRefungibleExData, budget::Budget, Property,
PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData,
};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
+use pallet_structure::Error as StructureError;
use sp_runtime::DispatchError;
use sp_std::{vec::Vec, vec};
@@ -113,6 +114,15 @@
fn burn_from() -> Weight {
<SelfWeightOf<T>>::burn_from()
}
+
+ fn burn_recursively_self_raw() -> Weight {
+ // Read to get total balance
+ Self::burn_item() + T::DbWeight::get().reads(1)
+ }
+ fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+ // Refungible token can't have children
+ 0
+ }
}
fn map_create_data<T: Config>(
@@ -205,6 +215,25 @@
)
}
+ fn burn_item_recursively(
+ &self,
+ sender: T::CrossAccountId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ _breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+ with_weight(
+ <Pallet<T>>::burn(
+ self,
+ &sender,
+ token,
+ <Balance<T>>::get((self.id, token, &sender)),
+ ),
+ <CommonWeights<T>>::burn_recursively_self_raw(),
+ )
+ }
+
fn transfer(
&self,
from: T::CrossAccountId,
pallets/scheduler/src/lib.rsdiffbeforeafterboth1// This file is part of Substrate.23// Copyright (C) 2017-2022 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//! # Schedulerdo_reschedule19//!20//! This Pallet exposes capabilities for scheduling dispatches to occur at a21//! specified block number or at a specified period. These scheduled dispatches22//! may be named or anonymous and may be canceled.23//!24//! **NOTE:** The scheduled calls will be dispatched with the default filter25//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin26//! except root which will get no filter. And not the filter contained in origin27//! use to call `fn schedule`.28//!29//! If a call is scheduled using proxy or whatever mecanism which adds filter,30//! then those filter will not be used when dispatching the schedule call.31//!32//! ## Interface33//!34//! ### Dispatchable Functions35//!36//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and37//! with a specified priority.38//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.39//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter40//! that can be used for identification.41//! * `cancel_named` - the named complement to the cancel function.4243// Ensure we're `no_std` when compiling for Wasm.44#![cfg_attr(not(feature = "std"), no_std)]4546#[cfg(feature = "runtime-benchmarks")]47mod benchmarking;4849pub mod weights;5051use sp_core::H160;52use codec::{Codec, Decode, Encode};53use frame_system::{self as system, ensure_signed};54pub use pallet::*;55use scale_info::TypeInfo;56use sp_runtime::{57 traits::{BadOrigin, One, Saturating, Zero},58 RuntimeDebug, DispatchErrorWithPostInfo,59};60use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};6162use frame_support::{63 dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter},64 traits::{65 schedule::{self, DispatchTime, MaybeHashed},66 NamedReservableCurrency, EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp,67 StorageVersion,68 },69 weights::{GetDispatchInfo, Weight},70};7172pub use weights::WeightInfo;7374/// Just a simple index for naming period tasks.75pub type PeriodicIndex = u32;76/// The location of a scheduled task that can be used to remove it.77pub type TaskAddress<BlockNumber> = (BlockNumber, u32);78pub const MAX_TASK_ID_LENGTH_IN_BYTES: u8 = 16;7980type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize];81pub type CallOrHashOf<T> = MaybeHashed<<T as Config>::Call, <T as frame_system::Config>::Hash>;8283/// Information regarding an item to be executed in the future.84#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]85#[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)]86pub struct ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId> {87 /// The unique identity for this task, if there is one.88 maybe_id: Option<ScheduledId>,89 /// This task's priority.90 priority: schedule::Priority,91 /// The call to be dispatched.92 call: Call,93 /// If the call is periodic, then this points to the information concerning that.94 maybe_periodic: Option<schedule::Period<BlockNumber>>,95 /// The origin to dispatch the call.96 origin: PalletsOrigin,97 _phantom: PhantomData<AccountId>,98}99100pub type ScheduledV3Of<T> = ScheduledV3<101 CallOrHashOf<T>,102 <T as frame_system::Config>::BlockNumber,103 <T as Config>::PalletsOrigin,104 <T as frame_system::Config>::AccountId,105>;106107pub type ScheduledOf<T> = ScheduledV3Of<T>;108109/// The current version of Scheduled struct.110pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =111 ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId>;112113#[cfg(feature = "runtime-benchmarks")]114mod preimage_provider {115 use frame_support::traits::PreimageRecipient;116 pub trait PreimageProviderAndMaybeRecipient<H>: PreimageRecipient<H> {}117 impl<H, T: PreimageRecipient<H>> PreimageProviderAndMaybeRecipient<H> for T {}118}119120#[cfg(not(feature = "runtime-benchmarks"))]121mod preimage_provider {122 use frame_support::traits::PreimageProvider;123 pub trait PreimageProviderAndMaybeRecipient<H>: PreimageProvider<H> {}124 impl<H, T: PreimageProvider<H>> PreimageProviderAndMaybeRecipient<H> for T {}125}126127pub use preimage_provider::PreimageProviderAndMaybeRecipient;128129pub(crate) trait MarginalWeightInfo: WeightInfo {130 fn item(periodic: bool, named: bool, resolved: Option<bool>) -> Weight {131 match (periodic, named, resolved) {132 (_, false, None) => Self::on_initialize_aborted(2) - Self::on_initialize_aborted(1),133 (_, true, None) => {134 Self::on_initialize_named_aborted(2) - Self::on_initialize_named_aborted(1)135 }136 (false, false, Some(false)) => Self::on_initialize(2) - Self::on_initialize(1),137 (false, true, Some(false)) => {138 Self::on_initialize_named(2) - Self::on_initialize_named(1)139 }140 (true, false, Some(false)) => {141 Self::on_initialize_periodic(2) - Self::on_initialize_periodic(1)142 }143 (true, true, Some(false)) => {144 Self::on_initialize_periodic_named(2) - Self::on_initialize_periodic_named(1)145 }146 (false, false, Some(true)) => {147 Self::on_initialize_resolved(2) - Self::on_initialize_resolved(1)148 }149 (false, true, Some(true)) => {150 Self::on_initialize_named_resolved(2) - Self::on_initialize_named_resolved(1)151 }152 (true, false, Some(true)) => {153 Self::on_initialize_periodic_resolved(2) - Self::on_initialize_periodic_resolved(1)154 }155 (true, true, Some(true)) => {156 Self::on_initialize_periodic_named_resolved(2)157 - Self::on_initialize_periodic_named_resolved(1)158 }159 }160 }161}162impl<T: WeightInfo> MarginalWeightInfo for T {}163164#[frame_support::pallet]165pub mod pallet {166 use super::*;167 use frame_support::{168 dispatch::PostDispatchInfo,169 pallet_prelude::*,170 traits::{schedule::LookupError, PreimageProvider},171 };172 use frame_system::pallet_prelude::*;173174 /// The current storage version.175 const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);176177 #[pallet::pallet]178 #[pallet::generate_store(pub(super) trait Store)]179 #[pallet::storage_version(STORAGE_VERSION)]180 #[pallet::without_storage_info]181 pub struct Pallet<T>(_);182183 /// `system::Config` should always be included in our implied traits.184 #[pallet::config]185 pub trait Config: frame_system::Config {186 /// The overarching event type.187 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;188189 /// The aggregated origin which the dispatch will take.190 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>191 + From<Self::PalletsOrigin>192 + IsType<<Self as system::Config>::Origin>;193194 /// The caller origin, overarching type of all pallets origins.195 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq + TypeInfo;196197 type Currency: NamedReservableCurrency<Self::AccountId, ReserveIdentifier = ScheduledId>;198199 /// The aggregated call type.200 type Call: Parameter201 + Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>202 + GetDispatchInfo203 + From<system::Call<Self>>;204205 /// The maximum weight that may be scheduled per block for any dispatchables of less206 /// priority than `schedule::HARD_DEADLINE`.207 #[pallet::constant]208 type MaximumWeight: Get<Weight>;209210 /// Required origin to schedule or cancel calls.211 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;212213 /// Compare the privileges of origins.214 ///215 /// This will be used when canceling a task, to ensure that the origin that tries216 /// to cancel has greater or equal privileges as the origin that created the scheduled task.217 ///218 /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can219 /// be used. This will only check if two given origins are equal.220 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;221222 /// The maximum number of scheduled calls in the queue for a single block.223 /// Not strictly enforced, but used for weight estimation.224 #[pallet::constant]225 type MaxScheduledPerBlock: Get<u32>;226227 /// Weight information for extrinsics in this pallet.228 type WeightInfo: WeightInfo;229230 /// The preimage provider with which we look up call hashes to get the call.231 type PreimageProvider: PreimageProviderAndMaybeRecipient<Self::Hash>;232233 /// If `Some` then the number of blocks to postpone execution for when the item is delayed.234 type NoPreimagePostponement: Get<Option<Self::BlockNumber>>;235236 /// Sponsoring function.237 // type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;238239 /// The helper type used for custom transaction fee logic.240 type CallExecutor: DispatchCall<Self, H160>;241 }242243 /// A Scheduler-Runtime interface for finer payment handling.244 pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {245 fn reserve_balance(246 id: ScheduledId,247 sponsor: <T as frame_system::Config>::AccountId,248 call: <T as Config>::Call,249 count: u32,250 ) -> Result<(), DispatchError>;251252 fn pay_for_call(253 id: ScheduledId,254 sponsor: <T as frame_system::Config>::AccountId,255 call: <T as Config>::Call,256 ) -> Result<u128, DispatchError>;257258 /// Resolve the call dispatch, including any post-dispatch operations.259 fn dispatch_call(260 signer: T::AccountId,261 function: <T as Config>::Call,262 ) -> Result<263 Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,264 TransactionValidityError,265 >;266267 fn cancel_reserve(268 id: ScheduledId,269 sponsor: <T as frame_system::Config>::AccountId,270 ) -> Result<u128, DispatchError>;271 }272273 /// Items to be executed, indexed by the block number that they should be executed on.274 #[pallet::storage]275 pub type Agenda<T: Config> =276 StorageMap<_, Twox64Concat, T::BlockNumber, Vec<Option<ScheduledV3Of<T>>>, ValueQuery>;277278 /// Lookup from identity to the block number and index of the task.279 #[pallet::storage]280 pub(crate) type Lookup<T: Config> =281 StorageMap<_, Twox64Concat, ScheduledId, TaskAddress<T::BlockNumber>>;282283 /// Events type.284 #[pallet::event]285 #[pallet::generate_deposit(pub(super) fn deposit_event)]286 pub enum Event<T: Config> {287 /// Scheduled some task.288 Scheduled { when: T::BlockNumber, index: u32 },289 /// Canceled some task.290 Canceled { when: T::BlockNumber, index: u32 },291 /// Dispatched some task.292 Dispatched {293 task: TaskAddress<T::BlockNumber>,294 id: Option<ScheduledId>,295 result: DispatchResult,296 },297 /// The call for the provided hash was not found so the task has been aborted.298 CallLookupFailed {299 task: TaskAddress<T::BlockNumber>,300 id: Option<ScheduledId>,301 error: LookupError,302 },303 }304305 #[pallet::error]306 pub enum Error<T> {307 /// Failed to schedule a call308 FailedToSchedule,309 /// Cannot find the scheduled call.310 NotFound,311 /// Given target block number is in the past.312 TargetBlockNumberInPast,313 /// Reschedule failed because it does not change scheduled time.314 RescheduleNoChange,315 }316317 #[pallet::hooks]318 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {319 /// Execute the scheduled calls320 fn on_initialize(now: T::BlockNumber) -> Weight {321 let limit = T::MaximumWeight::get();322323 let mut queued = Agenda::<T>::take(now)324 .into_iter()325 .enumerate()326 .filter_map(|(index, s)| Some((index as u32, s?)))327 .collect::<Vec<_>>();328329 if queued.len() as u32 > T::MaxScheduledPerBlock::get() {330 log::warn!(331 target: "runtime::scheduler",332 "Warning: This block has more items queued in Scheduler than \333 expected from the runtime configuration. An update might be needed."334 );335 }336337 queued.sort_by_key(|(_, s)| s.priority);338339 let next = now + One::one();340341 let mut total_weight: Weight = T::WeightInfo::on_initialize(0);342 for (order, (index, mut s)) in queued.into_iter().enumerate() {343 let named = if let Some(ref id) = s.maybe_id {344 Lookup::<T>::remove(id);345 true346 } else {347 false348 };349350 let (call, maybe_completed) = s.call.resolved::<T::PreimageProvider>();351 s.call = call;352353 let resolved = if let Some(completed) = maybe_completed {354 T::PreimageProvider::unrequest_preimage(&completed);355 true356 } else {357 false358 };359 let call = match s.call.as_value().cloned() {360 Some(c) => c,361 None => {362 // Preimage not available - postpone until some block.363 total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));364 if let Some(delay) = T::NoPreimagePostponement::get() {365 let until = now.saturating_add(delay);366 if let Some(ref id) = s.maybe_id {367 let index = Agenda::<T>::decode_len(until).unwrap_or(0);368 Lookup::<T>::insert(id, (until, index as u32));369 }370 Agenda::<T>::append(until, Some(s));371 }372 continue;373 }374 };375376 let periodic = s.maybe_periodic.is_some();377 let call_weight = call.get_dispatch_info().weight;378 let mut item_weight = T::WeightInfo::item(periodic, named, Some(resolved));379 let origin =380 <<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())381 .into();382 if ensure_signed(origin).is_ok() {383 // Weights of Signed dispatches expect their signing account to be whitelisted.384 item_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));385 }386387 // We allow a scheduled call if any is true:388 // - It's priority is `HARD_DEADLINE`389 // - It does not push the weight past the limit.390 // - It is the first item in the schedule391 let hard_deadline = s.priority <= schedule::HARD_DEADLINE;392 let test_weight = total_weight393 .saturating_add(call_weight)394 .saturating_add(item_weight);395 if !hard_deadline && order > 0 && test_weight > limit {396 // Cannot be scheduled this block - postpone until next.397 total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));398 if let Some(ref id) = s.maybe_id {399 // NOTE: We could reasonably not do this (in which case there would be one400 // block where the named and delayed item could not be referenced by name),401 // but we will do it anyway since it should be mostly free in terms of402 // weight and it is slightly cleaner.403 let index = Agenda::<T>::decode_len(next).unwrap_or(0);404 Lookup::<T>::insert(id, (next, index as u32));405 }406 Agenda::<T>::append(next, Some(s));407 continue;408 }409410 let sender = ensure_signed(411 <<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())412 .into(),413 )414 .unwrap();415416 // // if call have id it was be reserved417 // if s.maybe_id.is_some() {418 // let _ = T::CallExecutor::pay_for_call(419 // s.maybe_id.unwrap(),420 // sender.clone(),421 // call.clone(),422 // );423 // }424425 let r = T::CallExecutor::dispatch_call(sender, call.clone());426427 let mut actual_call_weight: Weight = item_weight;428 let result: Result<_, DispatchError> = match r {429 Ok(o) => match o {430 Ok(di) => {431 actual_call_weight = di.actual_weight.unwrap_or(item_weight);432 Ok(())433 }434 Err(err) => Err(err.error),435 },436 Err(_) => {437 log::error!(438 target: "runtime::scheduler",439 "Warning: Scheduler has failed to execute a post-dispatch transaction. \440 This block might have become invalid.");441 Err(DispatchError::CannotLookup)442 } // todo possibly force a skip/return here, do something with the error443 };444445 total_weight.saturating_accrue(item_weight);446 total_weight.saturating_accrue(actual_call_weight);447448 Self::deposit_event(Event::Dispatched {449 task: (now, index),450 id: s.maybe_id.clone(),451 result,452 });453454 if let &Some((period, count)) = &s.maybe_periodic {455 if count > 1 {456 s.maybe_periodic = Some((period, count - 1));457 } else {458 s.maybe_periodic = None;459 }460 let wake = now + period;461 // If scheduled is named, place its information in `Lookup`462 if let Some(ref id) = s.maybe_id {463 let wake_index = Agenda::<T>::decode_len(wake).unwrap_or(0);464 Lookup::<T>::insert(id, (wake, wake_index as u32));465 }466 Agenda::<T>::append(wake, Some(s));467 }468 }469 0470 //total_weight471 }472 }473474 #[pallet::call]475 impl<T: Config> Pallet<T> {476 /// Schedule a named task.477 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]478 pub fn schedule_named(479 origin: OriginFor<T>,480 id: ScheduledId,481 when: T::BlockNumber,482 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,483 priority: schedule::Priority,484 call: Box<CallOrHashOf<T>>,485 ) -> DispatchResult {486 T::ScheduleOrigin::ensure_origin(origin.clone())?;487 let origin = <T as Config>::Origin::from(origin);488 Self::do_schedule_named(489 id,490 DispatchTime::At(when),491 maybe_periodic,492 priority,493 origin.caller().clone(),494 *call,495 )?;496 Ok(())497 }498499 /// Cancel a named scheduled task.500 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]501 pub fn cancel_named(origin: OriginFor<T>, id: ScheduledId) -> DispatchResult {502 T::ScheduleOrigin::ensure_origin(origin.clone())?;503 let origin = <T as Config>::Origin::from(origin);504 Self::do_cancel_named(Some(origin.caller().clone()), id)?;505 Ok(())506 }507508 /// Schedule a named task after a delay.509 ///510 /// # <weight>511 /// Same as [`schedule_named`](Self::schedule_named).512 /// # </weight>513 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]514 pub fn schedule_named_after(515 origin: OriginFor<T>,516 id: ScheduledId,517 after: T::BlockNumber,518 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,519 priority: schedule::Priority,520 call: Box<CallOrHashOf<T>>,521 ) -> DispatchResult {522 T::ScheduleOrigin::ensure_origin(origin.clone())?;523 let origin = <T as Config>::Origin::from(origin);524 Self::do_schedule_named(525 id,526 DispatchTime::After(after),527 maybe_periodic,528 priority,529 origin.caller().clone(),530 *call,531 )?;532 Ok(())533 }534 }535}536537impl<T: Config> Pallet<T> {538 #[cfg(feature = "try-runtime")]539 pub fn pre_migrate_to_v3() -> Result<(), &'static str> {540 Ok(())541 }542543 #[cfg(feature = "try-runtime")]544 pub fn post_migrate_to_v3() -> Result<(), &'static str> {545 use frame_support::dispatch::GetStorageVersion;546547 assert!(Self::current_storage_version() == 3);548 for k in Agenda::<T>::iter_keys() {549 let _ = Agenda::<T>::try_get(k).map_err(|()| "Invalid item in Agenda")?;550 }551 Ok(())552 }553554 /// Helper to migrate scheduler when the pallet origin type has changed.555 pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {556 Agenda::<T>::translate::<557 Vec<Option<Scheduled<CallOrHashOf<T>, T::BlockNumber, OldOrigin, T::AccountId>>>,558 _,559 >(|_, agenda| {560 Some(561 agenda562 .into_iter()563 .map(|schedule| {564 schedule.map(|schedule| Scheduled {565 maybe_id: schedule.maybe_id,566 priority: schedule.priority,567 call: schedule.call,568 maybe_periodic: schedule.maybe_periodic,569 origin: schedule.origin.into(),570 _phantom: Default::default(),571 })572 })573 .collect::<Vec<_>>(),574 )575 });576 }577578 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {579 let now = frame_system::Pallet::<T>::block_number();580581 let when = match when {582 DispatchTime::At(x) => x,583 // The current block has already completed it's scheduled tasks, so584 // Schedule the task at lest one block after this current block.585 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),586 };587588 if when <= now {589 return Err(Error::<T>::TargetBlockNumberInPast.into());590 }591592 Ok(when)593 }594595 fn do_schedule(596 when: DispatchTime<T::BlockNumber>,597 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,598 priority: schedule::Priority,599 origin: T::PalletsOrigin,600 call: CallOrHashOf<T>,601 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {602 let when = Self::resolve_time(when)?;603 call.ensure_requested::<T::PreimageProvider>();604605 // sanitize maybe_periodic606 let maybe_periodic = maybe_periodic607 .filter(|p| p.1 > 1 && !p.0.is_zero())608 // Remove one from the number of repetitions since we will schedule one now.609 .map(|(p, c)| (p, c - 1));610 let s = Some(Scheduled {611 maybe_id: None,612 priority,613 call,614 maybe_periodic,615 origin,616 _phantom: PhantomData::<T::AccountId>::default(),617 });618 Agenda::<T>::append(when, s);619 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;620 Self::deposit_event(Event::Scheduled { when, index });621622 Ok((when, index))623 }624625 fn do_cancel(626 origin: Option<T::PalletsOrigin>,627 (when, index): TaskAddress<T::BlockNumber>,628 ) -> Result<(), DispatchError> {629 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {630 agenda.get_mut(index as usize).map_or(631 Ok(None),632 |s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {633 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {634 if matches!(635 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),636 Some(Ordering::Less) | None637 ) {638 return Err(BadOrigin.into());639 }640 };641 Ok(s.take())642 },643 )644 })?;645 if let Some(s) = scheduled {646 s.call.ensure_unrequested::<T::PreimageProvider>();647 if let Some(id) = s.maybe_id {648 Lookup::<T>::remove(id);649 }650 Self::deposit_event(Event::Canceled { when, index });651 Ok(())652 } else {653 Err(Error::<T>::NotFound)?654 }655 }656657 fn do_reschedule(658 (when, index): TaskAddress<T::BlockNumber>,659 new_time: DispatchTime<T::BlockNumber>,660 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {661 let new_time = Self::resolve_time(new_time)?;662663 if new_time == when {664 return Err(Error::<T>::RescheduleNoChange.into());665 }666667 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {668 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;669 let task = task.take().ok_or(Error::<T>::NotFound)?;670 Agenda::<T>::append(new_time, Some(task));671 Ok(())672 })?;673674 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;675 Self::deposit_event(Event::Canceled { when, index });676 Self::deposit_event(Event::Scheduled {677 when: new_time,678 index: new_index,679 });680681 Ok((new_time, new_index))682 }683684 fn do_schedule_named(685 id: ScheduledId,686 when: DispatchTime<T::BlockNumber>,687 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,688 priority: schedule::Priority,689 origin: T::PalletsOrigin,690 call: CallOrHashOf<T>,691 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {692 // ensure id it is unique693 if Lookup::<T>::contains_key(&id) {694 return Err(Error::<T>::FailedToSchedule)?;695 }696697 let when = Self::resolve_time(when)?;698699 call.ensure_requested::<T::PreimageProvider>();700701 // sanitize maybe_periodic702 let maybe_periodic = maybe_periodic703 .filter(|p| p.1 > 1 && !p.0.is_zero())704 // Remove one from the number of repetitions since we will schedule one now.705 .map(|(p, c)| (p, c - 1));706707 let s = Scheduled {708 maybe_id: Some(id.clone()),709 priority,710 call: call.clone(),711 maybe_periodic,712 origin: origin.clone(),713 _phantom: Default::default(),714 };715716 // reserve balance for periodic execution717 // let sender =718 // ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;719 // let repeats = match maybe_periodic {720 // Some(p) => p.1,721 // None => 1,722 // };723 // let _ = T::CallExecutor::reserve_balance(724 // id.clone(),725 // sender,726 // call.as_value().unwrap().clone(),727 // repeats,728 // );729730 Agenda::<T>::append(when, Some(s));731 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;732 let address = (when, index);733 Lookup::<T>::insert(&id, &address);734 Self::deposit_event(Event::Scheduled { when, index });735736 Ok(address)737 }738739 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: ScheduledId) -> DispatchResult {740 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {741 if let Some((when, index)) = lookup.take() {742 let i = index as usize;743 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {744 if let Some(s) = agenda.get_mut(i) {745 if let (Some(ref o), Some(ref s)) = (origin.clone(), s.borrow()) {746 if matches!(747 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),748 Some(Ordering::Less) | None749 ) {750 return Err(BadOrigin.into());751 }752 // release balance reserve753 // let sender = ensure_signed(754 // <<T as Config>::Origin as From<T::PalletsOrigin>>::from(755 // origin.unwrap(),756 // )757 // .into(),758 // )?;759 // let _ = T::CallExecutor::cancel_reserve(id, sender);760761 s.call.ensure_unrequested::<T::PreimageProvider>();762 }763 *s = None;764 }765 Ok(())766 })?;767768 Self::deposit_event(Event::Canceled { when, index });769 Ok(())770 } else {771 Err(Error::<T>::NotFound)?772 }773 })774 }775776 fn do_reschedule_named(777 id: ScheduledId,778 new_time: DispatchTime<T::BlockNumber>,779 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {780 let new_time = Self::resolve_time(new_time)?;781782 Lookup::<T>::try_mutate_exists(783 id,784 |lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {785 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;786787 if new_time == when {788 return Err(Error::<T>::RescheduleNoChange.into());789 }790791 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {792 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;793 let task = task.take().ok_or(Error::<T>::NotFound)?;794 Agenda::<T>::append(new_time, Some(task));795796 Ok(())797 })?;798799 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;800 Self::deposit_event(Event::Canceled { when, index });801 Self::deposit_event(Event::Scheduled {802 when: new_time,803 index: new_index,804 });805806 *lookup = Some((new_time, new_index));807808 Ok((new_time, new_index))809 },810 )811 }812}813814impl<T: Config> schedule::v2::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>815 for Pallet<T>816{817 type Address = TaskAddress<T::BlockNumber>;818 type Hash = T::Hash;819820 fn schedule(821 when: DispatchTime<T::BlockNumber>,822 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,823 priority: schedule::Priority,824 origin: T::PalletsOrigin,825 call: CallOrHashOf<T>,826 ) -> Result<Self::Address, DispatchError> {827 Self::do_schedule(when, maybe_periodic, priority, origin, call)828 }829830 fn cancel((when, index): Self::Address) -> Result<(), ()> {831 Self::do_cancel(None, (when, index)).map_err(|_| ())832 }833834 fn reschedule(835 address: Self::Address,836 when: DispatchTime<T::BlockNumber>,837 ) -> Result<Self::Address, DispatchError> {838 Self::do_reschedule(address, when)839 }840841 fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {842 Agenda::<T>::get(when)843 .get(index as usize)844 .ok_or(())845 .map(|_| when)846 }847}848849impl<T: Config> schedule::v2::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>850 for Pallet<T>851{852 type Address = TaskAddress<T::BlockNumber>;853 type Hash = T::Hash;854855 fn schedule_named(856 id: Vec<u8>,857 when: DispatchTime<T::BlockNumber>,858 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,859 priority: schedule::Priority,860 origin: T::PalletsOrigin,861 call: CallOrHashOf<T>,862 ) -> Result<Self::Address, ()> {863 let inner_id: ScheduledId = id864 .try_into()865 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);866 Self::do_schedule_named(inner_id, when, maybe_periodic, priority, origin, call)867 .map_err(|_| ())868 }869870 fn cancel_named(id: Vec<u8>) -> Result<(), ()> {871 let inner_id: ScheduledId = id872 .try_into()873 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);874 Self::do_cancel_named(None, inner_id).map_err(|_| ())875 }876877 fn reschedule_named(878 id: Vec<u8>,879 when: DispatchTime<T::BlockNumber>,880 ) -> Result<Self::Address, DispatchError> {881 let inner_id: ScheduledId = id882 .try_into()883 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);884 Self::do_reschedule_named(inner_id, when)885 }886887 fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {888 let inner_id: ScheduledId = id889 .try_into()890 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);891 Lookup::<T>::get(inner_id)892 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))893 .ok_or(())894 }895}1// This file is part of Substrate.23// Copyright (C) 2017-2022 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//! # Schedulerdo_reschedule19//!20//! This Pallet exposes capabilities for scheduling dispatches to occur at a21//! specified block number or at a specified period. These scheduled dispatches22//! may be named or anonymous and may be canceled.23//!24//! **NOTE:** The scheduled calls will be dispatched with the default filter25//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin26//! except root which will get no filter. And not the filter contained in origin27//! use to call `fn schedule`.28//!29//! If a call is scheduled using proxy or whatever mecanism which adds filter,30//! then those filter will not be used when dispatching the schedule call.31//!32//! ## Interface33//!34//! ### Dispatchable Functions35//!36//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and37//! with a specified priority.38//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.39//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter40//! that can be used for identification.41//! * `cancel_named` - the named complement to the cancel function.4243// Ensure we're `no_std` when compiling for Wasm.44#![cfg_attr(not(feature = "std"), no_std)]4546// FIXME47// #[cfg(feature = "runtime-benchmarks")]48// mod benchmarking;4950pub mod weights;5152use sp_core::H160;53use codec::{Codec, Decode, Encode};54use frame_system::{self as system, ensure_signed};55pub use pallet::*;56use scale_info::TypeInfo;57use sp_runtime::{58 traits::{BadOrigin, One, Saturating, Zero},59 RuntimeDebug, DispatchErrorWithPostInfo,60};61use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*};6263use frame_support::{64 dispatch::{DispatchError, DispatchResult, Dispatchable, Parameter},65 traits::{66 schedule::{self, DispatchTime, MaybeHashed},67 NamedReservableCurrency, EnsureOrigin, Get, IsType, OriginTrait, PrivilegeCmp,68 StorageVersion,69 },70 weights::{GetDispatchInfo, Weight},71};7273pub use weights::WeightInfo;7475/// Just a simple index for naming period tasks.76pub type PeriodicIndex = u32;77/// The location of a scheduled task that can be used to remove it.78pub type TaskAddress<BlockNumber> = (BlockNumber, u32);79pub const MAX_TASK_ID_LENGTH_IN_BYTES: u8 = 16;8081type ScheduledId = [u8; MAX_TASK_ID_LENGTH_IN_BYTES as usize];82pub type CallOrHashOf<T> = MaybeHashed<<T as Config>::Call, <T as frame_system::Config>::Hash>;8384/// Information regarding an item to be executed in the future.85#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]86#[derive(Clone, RuntimeDebug, Encode, Decode, TypeInfo)]87pub struct ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId> {88 /// The unique identity for this task, if there is one.89 maybe_id: Option<ScheduledId>,90 /// This task's priority.91 priority: schedule::Priority,92 /// The call to be dispatched.93 call: Call,94 /// If the call is periodic, then this points to the information concerning that.95 maybe_periodic: Option<schedule::Period<BlockNumber>>,96 /// The origin to dispatch the call.97 origin: PalletsOrigin,98 _phantom: PhantomData<AccountId>,99}100101pub type ScheduledV3Of<T> = ScheduledV3<102 CallOrHashOf<T>,103 <T as frame_system::Config>::BlockNumber,104 <T as Config>::PalletsOrigin,105 <T as frame_system::Config>::AccountId,106>;107108pub type ScheduledOf<T> = ScheduledV3Of<T>;109110/// The current version of Scheduled struct.111pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> =112 ScheduledV3<Call, BlockNumber, PalletsOrigin, AccountId>;113114#[cfg(feature = "runtime-benchmarks")]115mod preimage_provider {116 use frame_support::traits::PreimageRecipient;117 pub trait PreimageProviderAndMaybeRecipient<H>: PreimageRecipient<H> {}118 impl<H, T: PreimageRecipient<H>> PreimageProviderAndMaybeRecipient<H> for T {}119}120121#[cfg(not(feature = "runtime-benchmarks"))]122mod preimage_provider {123 use frame_support::traits::PreimageProvider;124 pub trait PreimageProviderAndMaybeRecipient<H>: PreimageProvider<H> {}125 impl<H, T: PreimageProvider<H>> PreimageProviderAndMaybeRecipient<H> for T {}126}127128pub use preimage_provider::PreimageProviderAndMaybeRecipient;129130pub(crate) trait MarginalWeightInfo: WeightInfo {131 fn item(periodic: bool, named: bool, resolved: Option<bool>) -> Weight {132 match (periodic, named, resolved) {133 (_, false, None) => Self::on_initialize_aborted(2) - Self::on_initialize_aborted(1),134 (_, true, None) => {135 Self::on_initialize_named_aborted(2) - Self::on_initialize_named_aborted(1)136 }137 (false, false, Some(false)) => Self::on_initialize(2) - Self::on_initialize(1),138 (false, true, Some(false)) => {139 Self::on_initialize_named(2) - Self::on_initialize_named(1)140 }141 (true, false, Some(false)) => {142 Self::on_initialize_periodic(2) - Self::on_initialize_periodic(1)143 }144 (true, true, Some(false)) => {145 Self::on_initialize_periodic_named(2) - Self::on_initialize_periodic_named(1)146 }147 (false, false, Some(true)) => {148 Self::on_initialize_resolved(2) - Self::on_initialize_resolved(1)149 }150 (false, true, Some(true)) => {151 Self::on_initialize_named_resolved(2) - Self::on_initialize_named_resolved(1)152 }153 (true, false, Some(true)) => {154 Self::on_initialize_periodic_resolved(2) - Self::on_initialize_periodic_resolved(1)155 }156 (true, true, Some(true)) => {157 Self::on_initialize_periodic_named_resolved(2)158 - Self::on_initialize_periodic_named_resolved(1)159 }160 }161 }162}163impl<T: WeightInfo> MarginalWeightInfo for T {}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use frame_support::{169 dispatch::PostDispatchInfo,170 pallet_prelude::*,171 traits::{schedule::LookupError, PreimageProvider},172 };173 use frame_system::pallet_prelude::*;174175 /// The current storage version.176 const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);177178 #[pallet::pallet]179 #[pallet::generate_store(pub(super) trait Store)]180 #[pallet::storage_version(STORAGE_VERSION)]181 #[pallet::without_storage_info]182 pub struct Pallet<T>(_);183184 /// `system::Config` should always be included in our implied traits.185 #[pallet::config]186 pub trait Config: frame_system::Config {187 /// The overarching event type.188 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;189190 /// The aggregated origin which the dispatch will take.191 type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>192 + From<Self::PalletsOrigin>193 + IsType<<Self as system::Config>::Origin>;194195 /// The caller origin, overarching type of all pallets origins.196 type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq + TypeInfo;197198 type Currency: NamedReservableCurrency<Self::AccountId, ReserveIdentifier = ScheduledId>;199200 /// The aggregated call type.201 type Call: Parameter202 + Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>203 + GetDispatchInfo204 + From<system::Call<Self>>;205206 /// The maximum weight that may be scheduled per block for any dispatchables of less207 /// priority than `schedule::HARD_DEADLINE`.208 #[pallet::constant]209 type MaximumWeight: Get<Weight>;210211 /// Required origin to schedule or cancel calls.212 type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;213214 /// Compare the privileges of origins.215 ///216 /// This will be used when canceling a task, to ensure that the origin that tries217 /// to cancel has greater or equal privileges as the origin that created the scheduled task.218 ///219 /// For simplicity the [`EqualPrivilegeOnly`](frame_support::traits::EqualPrivilegeOnly) can220 /// be used. This will only check if two given origins are equal.221 type OriginPrivilegeCmp: PrivilegeCmp<Self::PalletsOrigin>;222223 /// The maximum number of scheduled calls in the queue for a single block.224 /// Not strictly enforced, but used for weight estimation.225 #[pallet::constant]226 type MaxScheduledPerBlock: Get<u32>;227228 /// Weight information for extrinsics in this pallet.229 type WeightInfo: WeightInfo;230231 /// The preimage provider with which we look up call hashes to get the call.232 type PreimageProvider: PreimageProviderAndMaybeRecipient<Self::Hash>;233234 /// If `Some` then the number of blocks to postpone execution for when the item is delayed.235 type NoPreimagePostponement: Get<Option<Self::BlockNumber>>;236237 /// Sponsoring function.238 // type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;239240 /// The helper type used for custom transaction fee logic.241 type CallExecutor: DispatchCall<Self, H160>;242 }243244 /// A Scheduler-Runtime interface for finer payment handling.245 pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {246 fn reserve_balance(247 id: ScheduledId,248 sponsor: <T as frame_system::Config>::AccountId,249 call: <T as Config>::Call,250 count: u32,251 ) -> Result<(), DispatchError>;252253 fn pay_for_call(254 id: ScheduledId,255 sponsor: <T as frame_system::Config>::AccountId,256 call: <T as Config>::Call,257 ) -> Result<u128, DispatchError>;258259 /// Resolve the call dispatch, including any post-dispatch operations.260 fn dispatch_call(261 signer: T::AccountId,262 function: <T as Config>::Call,263 ) -> Result<264 Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,265 TransactionValidityError,266 >;267268 fn cancel_reserve(269 id: ScheduledId,270 sponsor: <T as frame_system::Config>::AccountId,271 ) -> Result<u128, DispatchError>;272 }273274 /// Items to be executed, indexed by the block number that they should be executed on.275 #[pallet::storage]276 pub type Agenda<T: Config> =277 StorageMap<_, Twox64Concat, T::BlockNumber, Vec<Option<ScheduledV3Of<T>>>, ValueQuery>;278279 /// Lookup from identity to the block number and index of the task.280 #[pallet::storage]281 pub(crate) type Lookup<T: Config> =282 StorageMap<_, Twox64Concat, ScheduledId, TaskAddress<T::BlockNumber>>;283284 /// Events type.285 #[pallet::event]286 #[pallet::generate_deposit(pub(super) fn deposit_event)]287 pub enum Event<T: Config> {288 /// Scheduled some task.289 Scheduled { when: T::BlockNumber, index: u32 },290 /// Canceled some task.291 Canceled { when: T::BlockNumber, index: u32 },292 /// Dispatched some task.293 Dispatched {294 task: TaskAddress<T::BlockNumber>,295 id: Option<ScheduledId>,296 result: DispatchResult,297 },298 /// The call for the provided hash was not found so the task has been aborted.299 CallLookupFailed {300 task: TaskAddress<T::BlockNumber>,301 id: Option<ScheduledId>,302 error: LookupError,303 },304 }305306 #[pallet::error]307 pub enum Error<T> {308 /// Failed to schedule a call309 FailedToSchedule,310 /// Cannot find the scheduled call.311 NotFound,312 /// Given target block number is in the past.313 TargetBlockNumberInPast,314 /// Reschedule failed because it does not change scheduled time.315 RescheduleNoChange,316 }317318 #[pallet::hooks]319 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {320 /// Execute the scheduled calls321 fn on_initialize(now: T::BlockNumber) -> Weight {322 let limit = T::MaximumWeight::get();323324 let mut queued = Agenda::<T>::take(now)325 .into_iter()326 .enumerate()327 .filter_map(|(index, s)| Some((index as u32, s?)))328 .collect::<Vec<_>>();329330 if queued.len() as u32 > T::MaxScheduledPerBlock::get() {331 log::warn!(332 target: "runtime::scheduler",333 "Warning: This block has more items queued in Scheduler than \334 expected from the runtime configuration. An update might be needed."335 );336 }337338 queued.sort_by_key(|(_, s)| s.priority);339340 let next = now + One::one();341342 let mut total_weight: Weight = T::WeightInfo::on_initialize(0);343 for (order, (index, mut s)) in queued.into_iter().enumerate() {344 let named = if let Some(ref id) = s.maybe_id {345 Lookup::<T>::remove(id);346 true347 } else {348 false349 };350351 let (call, maybe_completed) = s.call.resolved::<T::PreimageProvider>();352 s.call = call;353354 let resolved = if let Some(completed) = maybe_completed {355 T::PreimageProvider::unrequest_preimage(&completed);356 true357 } else {358 false359 };360 let call = match s.call.as_value().cloned() {361 Some(c) => c,362 None => {363 // Preimage not available - postpone until some block.364 total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));365 if let Some(delay) = T::NoPreimagePostponement::get() {366 let until = now.saturating_add(delay);367 if let Some(ref id) = s.maybe_id {368 let index = Agenda::<T>::decode_len(until).unwrap_or(0);369 Lookup::<T>::insert(id, (until, index as u32));370 }371 Agenda::<T>::append(until, Some(s));372 }373 continue;374 }375 };376377 let periodic = s.maybe_periodic.is_some();378 let call_weight = call.get_dispatch_info().weight;379 let mut item_weight = T::WeightInfo::item(periodic, named, Some(resolved));380 let origin =381 <<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())382 .into();383 if ensure_signed(origin).is_ok() {384 // Weights of Signed dispatches expect their signing account to be whitelisted.385 item_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));386 }387388 // We allow a scheduled call if any is true:389 // - It's priority is `HARD_DEADLINE`390 // - It does not push the weight past the limit.391 // - It is the first item in the schedule392 let hard_deadline = s.priority <= schedule::HARD_DEADLINE;393 let test_weight = total_weight394 .saturating_add(call_weight)395 .saturating_add(item_weight);396 if !hard_deadline && order > 0 && test_weight > limit {397 // Cannot be scheduled this block - postpone until next.398 total_weight.saturating_accrue(T::WeightInfo::item(false, named, None));399 if let Some(ref id) = s.maybe_id {400 // NOTE: We could reasonably not do this (in which case there would be one401 // block where the named and delayed item could not be referenced by name),402 // but we will do it anyway since it should be mostly free in terms of403 // weight and it is slightly cleaner.404 let index = Agenda::<T>::decode_len(next).unwrap_or(0);405 Lookup::<T>::insert(id, (next, index as u32));406 }407 Agenda::<T>::append(next, Some(s));408 continue;409 }410411 let sender = ensure_signed(412 <<T as Config>::Origin as From<T::PalletsOrigin>>::from(s.origin.clone())413 .into(),414 )415 .unwrap();416417 // // if call have id it was be reserved418 // if s.maybe_id.is_some() {419 // let _ = T::CallExecutor::pay_for_call(420 // s.maybe_id.unwrap(),421 // sender.clone(),422 // call.clone(),423 // );424 // }425426 let r = T::CallExecutor::dispatch_call(sender, call.clone());427428 let mut actual_call_weight: Weight = item_weight;429 let result: Result<_, DispatchError> = match r {430 Ok(o) => match o {431 Ok(di) => {432 actual_call_weight = di.actual_weight.unwrap_or(item_weight);433 Ok(())434 }435 Err(err) => Err(err.error),436 },437 Err(_) => {438 log::error!(439 target: "runtime::scheduler",440 "Warning: Scheduler has failed to execute a post-dispatch transaction. \441 This block might have become invalid.");442 Err(DispatchError::CannotLookup)443 } // todo possibly force a skip/return here, do something with the error444 };445446 total_weight.saturating_accrue(item_weight);447 total_weight.saturating_accrue(actual_call_weight);448449 Self::deposit_event(Event::Dispatched {450 task: (now, index),451 id: s.maybe_id.clone(),452 result,453 });454455 if let &Some((period, count)) = &s.maybe_periodic {456 if count > 1 {457 s.maybe_periodic = Some((period, count - 1));458 } else {459 s.maybe_periodic = None;460 }461 let wake = now + period;462 // If scheduled is named, place its information in `Lookup`463 if let Some(ref id) = s.maybe_id {464 let wake_index = Agenda::<T>::decode_len(wake).unwrap_or(0);465 Lookup::<T>::insert(id, (wake, wake_index as u32));466 }467 Agenda::<T>::append(wake, Some(s));468 }469 }470 0471 //total_weight472 }473 }474475 #[pallet::call]476 impl<T: Config> Pallet<T> {477 /// Schedule a named task.478 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]479 pub fn schedule_named(480 origin: OriginFor<T>,481 id: ScheduledId,482 when: T::BlockNumber,483 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,484 priority: schedule::Priority,485 call: Box<CallOrHashOf<T>>,486 ) -> DispatchResult {487 T::ScheduleOrigin::ensure_origin(origin.clone())?;488 let origin = <T as Config>::Origin::from(origin);489 Self::do_schedule_named(490 id,491 DispatchTime::At(when),492 maybe_periodic,493 priority,494 origin.caller().clone(),495 *call,496 )?;497 Ok(())498 }499500 /// Cancel a named scheduled task.501 #[pallet::weight(<T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get()))]502 pub fn cancel_named(origin: OriginFor<T>, id: ScheduledId) -> DispatchResult {503 T::ScheduleOrigin::ensure_origin(origin.clone())?;504 let origin = <T as Config>::Origin::from(origin);505 Self::do_cancel_named(Some(origin.caller().clone()), id)?;506 Ok(())507 }508509 /// Schedule a named task after a delay.510 ///511 /// # <weight>512 /// Same as [`schedule_named`](Self::schedule_named).513 /// # </weight>514 #[pallet::weight(<T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))]515 pub fn schedule_named_after(516 origin: OriginFor<T>,517 id: ScheduledId,518 after: T::BlockNumber,519 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,520 priority: schedule::Priority,521 call: Box<CallOrHashOf<T>>,522 ) -> DispatchResult {523 T::ScheduleOrigin::ensure_origin(origin.clone())?;524 let origin = <T as Config>::Origin::from(origin);525 Self::do_schedule_named(526 id,527 DispatchTime::After(after),528 maybe_periodic,529 priority,530 origin.caller().clone(),531 *call,532 )?;533 Ok(())534 }535 }536}537538impl<T: Config> Pallet<T> {539 #[cfg(feature = "try-runtime")]540 pub fn pre_migrate_to_v3() -> Result<(), &'static str> {541 Ok(())542 }543544 #[cfg(feature = "try-runtime")]545 pub fn post_migrate_to_v3() -> Result<(), &'static str> {546 use frame_support::dispatch::GetStorageVersion;547548 assert!(Self::current_storage_version() == 3);549 for k in Agenda::<T>::iter_keys() {550 let _ = Agenda::<T>::try_get(k).map_err(|()| "Invalid item in Agenda")?;551 }552 Ok(())553 }554555 /// Helper to migrate scheduler when the pallet origin type has changed.556 pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {557 Agenda::<T>::translate::<558 Vec<Option<Scheduled<CallOrHashOf<T>, T::BlockNumber, OldOrigin, T::AccountId>>>,559 _,560 >(|_, agenda| {561 Some(562 agenda563 .into_iter()564 .map(|schedule| {565 schedule.map(|schedule| Scheduled {566 maybe_id: schedule.maybe_id,567 priority: schedule.priority,568 call: schedule.call,569 maybe_periodic: schedule.maybe_periodic,570 origin: schedule.origin.into(),571 _phantom: Default::default(),572 })573 })574 .collect::<Vec<_>>(),575 )576 });577 }578579 fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {580 let now = frame_system::Pallet::<T>::block_number();581582 let when = match when {583 DispatchTime::At(x) => x,584 // The current block has already completed it's scheduled tasks, so585 // Schedule the task at lest one block after this current block.586 DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one()),587 };588589 if when <= now {590 return Err(Error::<T>::TargetBlockNumberInPast.into());591 }592593 Ok(when)594 }595596 fn do_schedule(597 when: DispatchTime<T::BlockNumber>,598 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,599 priority: schedule::Priority,600 origin: T::PalletsOrigin,601 call: CallOrHashOf<T>,602 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {603 let when = Self::resolve_time(when)?;604 call.ensure_requested::<T::PreimageProvider>();605606 // sanitize maybe_periodic607 let maybe_periodic = maybe_periodic608 .filter(|p| p.1 > 1 && !p.0.is_zero())609 // Remove one from the number of repetitions since we will schedule one now.610 .map(|(p, c)| (p, c - 1));611 let s = Some(Scheduled {612 maybe_id: None,613 priority,614 call,615 maybe_periodic,616 origin,617 _phantom: PhantomData::<T::AccountId>::default(),618 });619 Agenda::<T>::append(when, s);620 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;621 Self::deposit_event(Event::Scheduled { when, index });622623 Ok((when, index))624 }625626 fn do_cancel(627 origin: Option<T::PalletsOrigin>,628 (when, index): TaskAddress<T::BlockNumber>,629 ) -> Result<(), DispatchError> {630 let scheduled = Agenda::<T>::try_mutate(when, |agenda| {631 agenda.get_mut(index as usize).map_or(632 Ok(None),633 |s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {634 if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {635 if matches!(636 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),637 Some(Ordering::Less) | None638 ) {639 return Err(BadOrigin.into());640 }641 };642 Ok(s.take())643 },644 )645 })?;646 if let Some(s) = scheduled {647 s.call.ensure_unrequested::<T::PreimageProvider>();648 if let Some(id) = s.maybe_id {649 Lookup::<T>::remove(id);650 }651 Self::deposit_event(Event::Canceled { when, index });652 Ok(())653 } else {654 Err(Error::<T>::NotFound)?655 }656 }657658 fn do_reschedule(659 (when, index): TaskAddress<T::BlockNumber>,660 new_time: DispatchTime<T::BlockNumber>,661 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {662 let new_time = Self::resolve_time(new_time)?;663664 if new_time == when {665 return Err(Error::<T>::RescheduleNoChange.into());666 }667668 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {669 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;670 let task = task.take().ok_or(Error::<T>::NotFound)?;671 Agenda::<T>::append(new_time, Some(task));672 Ok(())673 })?;674675 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;676 Self::deposit_event(Event::Canceled { when, index });677 Self::deposit_event(Event::Scheduled {678 when: new_time,679 index: new_index,680 });681682 Ok((new_time, new_index))683 }684685 fn do_schedule_named(686 id: ScheduledId,687 when: DispatchTime<T::BlockNumber>,688 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,689 priority: schedule::Priority,690 origin: T::PalletsOrigin,691 call: CallOrHashOf<T>,692 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {693 // ensure id it is unique694 if Lookup::<T>::contains_key(&id) {695 return Err(Error::<T>::FailedToSchedule)?;696 }697698 let when = Self::resolve_time(when)?;699700 call.ensure_requested::<T::PreimageProvider>();701702 // sanitize maybe_periodic703 let maybe_periodic = maybe_periodic704 .filter(|p| p.1 > 1 && !p.0.is_zero())705 // Remove one from the number of repetitions since we will schedule one now.706 .map(|(p, c)| (p, c - 1));707708 let s = Scheduled {709 maybe_id: Some(id.clone()),710 priority,711 call: call.clone(),712 maybe_periodic,713 origin: origin.clone(),714 _phantom: Default::default(),715 };716717 // reserve balance for periodic execution718 // let sender =719 // ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;720 // let repeats = match maybe_periodic {721 // Some(p) => p.1,722 // None => 1,723 // };724 // let _ = T::CallExecutor::reserve_balance(725 // id.clone(),726 // sender,727 // call.as_value().unwrap().clone(),728 // repeats,729 // );730731 Agenda::<T>::append(when, Some(s));732 let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;733 let address = (when, index);734 Lookup::<T>::insert(&id, &address);735 Self::deposit_event(Event::Scheduled { when, index });736737 Ok(address)738 }739740 fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: ScheduledId) -> DispatchResult {741 Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {742 if let Some((when, index)) = lookup.take() {743 let i = index as usize;744 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {745 if let Some(s) = agenda.get_mut(i) {746 if let (Some(ref o), Some(ref s)) = (origin.clone(), s.borrow()) {747 if matches!(748 T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin),749 Some(Ordering::Less) | None750 ) {751 return Err(BadOrigin.into());752 }753 // release balance reserve754 // let sender = ensure_signed(755 // <<T as Config>::Origin as From<T::PalletsOrigin>>::from(756 // origin.unwrap(),757 // )758 // .into(),759 // )?;760 // let _ = T::CallExecutor::cancel_reserve(id, sender);761762 s.call.ensure_unrequested::<T::PreimageProvider>();763 }764 *s = None;765 }766 Ok(())767 })?;768769 Self::deposit_event(Event::Canceled { when, index });770 Ok(())771 } else {772 Err(Error::<T>::NotFound)?773 }774 })775 }776777 fn do_reschedule_named(778 id: ScheduledId,779 new_time: DispatchTime<T::BlockNumber>,780 ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {781 let new_time = Self::resolve_time(new_time)?;782783 Lookup::<T>::try_mutate_exists(784 id,785 |lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {786 let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;787788 if new_time == when {789 return Err(Error::<T>::RescheduleNoChange.into());790 }791792 Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {793 let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;794 let task = task.take().ok_or(Error::<T>::NotFound)?;795 Agenda::<T>::append(new_time, Some(task));796797 Ok(())798 })?;799800 let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;801 Self::deposit_event(Event::Canceled { when, index });802 Self::deposit_event(Event::Scheduled {803 when: new_time,804 index: new_index,805 });806807 *lookup = Some((new_time, new_index));808809 Ok((new_time, new_index))810 },811 )812 }813}814815impl<T: Config> schedule::v2::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>816 for Pallet<T>817{818 type Address = TaskAddress<T::BlockNumber>;819 type Hash = T::Hash;820821 fn schedule(822 when: DispatchTime<T::BlockNumber>,823 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,824 priority: schedule::Priority,825 origin: T::PalletsOrigin,826 call: CallOrHashOf<T>,827 ) -> Result<Self::Address, DispatchError> {828 Self::do_schedule(when, maybe_periodic, priority, origin, call)829 }830831 fn cancel((when, index): Self::Address) -> Result<(), ()> {832 Self::do_cancel(None, (when, index)).map_err(|_| ())833 }834835 fn reschedule(836 address: Self::Address,837 when: DispatchTime<T::BlockNumber>,838 ) -> Result<Self::Address, DispatchError> {839 Self::do_reschedule(address, when)840 }841842 fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {843 Agenda::<T>::get(when)844 .get(index as usize)845 .ok_or(())846 .map(|_| when)847 }848}849850impl<T: Config> schedule::v2::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin>851 for Pallet<T>852{853 type Address = TaskAddress<T::BlockNumber>;854 type Hash = T::Hash;855856 fn schedule_named(857 id: Vec<u8>,858 when: DispatchTime<T::BlockNumber>,859 maybe_periodic: Option<schedule::Period<T::BlockNumber>>,860 priority: schedule::Priority,861 origin: T::PalletsOrigin,862 call: CallOrHashOf<T>,863 ) -> Result<Self::Address, ()> {864 let inner_id: ScheduledId = id865 .try_into()866 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);867 Self::do_schedule_named(inner_id, when, maybe_periodic, priority, origin, call)868 .map_err(|_| ())869 }870871 fn cancel_named(id: Vec<u8>) -> Result<(), ()> {872 let inner_id: ScheduledId = id873 .try_into()874 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);875 Self::do_cancel_named(None, inner_id).map_err(|_| ())876 }877878 fn reschedule_named(879 id: Vec<u8>,880 when: DispatchTime<T::BlockNumber>,881 ) -> Result<Self::Address, DispatchError> {882 let inner_id: ScheduledId = id883 .try_into()884 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);885 Self::do_reschedule_named(inner_id, when)886 }887888 fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {889 let inner_id: ScheduledId = id890 .try_into()891 .unwrap_or([0; MAX_TASK_ID_LENGTH_IN_BYTES as usize]);892 Lookup::<T>::get(inner_id)893 .and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when))894 .ok_or(())895 }896}pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -5,6 +5,7 @@
use up_data_structs::{
CreateCollectionData, CollectionMode, CreateItemData, CreateNftData, budget::Unlimited,
};
+use pallet_common::Config as CommonConfig;
use pallet_evm::account::CrossAccountId;
const SEED: u32 = 1;
@@ -14,8 +15,8 @@
let caller: T::AccountId = account("caller", 0, SEED);
let caller_cross = T::CrossAccountId::from_sub(caller.clone());
- T::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
- T::CollectionDispatch::create(caller, CreateCollectionData {
+ <T as CommonConfig>::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
+ T::CollectionDispatch::create(caller_cross.clone(), CreateCollectionData {
mode: CollectionMode::NFT,
..Default::default()
})?;
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -3,7 +3,7 @@
use pallet_common::CommonCollectionOperations;
use sp_std::collections::btree_set::BTreeSet;
-use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
use frame_support::fail;
pub use pallet::*;
use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
@@ -29,6 +29,8 @@
OuroborosDetected,
/// While searched for owner, encountered depth limit
DepthLimit,
+ /// While iterating over children, encountered breadth limit
+ BreadthLimit,
/// While searched for owner, found token owner by not-yet-existing token
TokenNotFound,
}
@@ -184,6 +186,19 @@
Err(<Error<T>>::DepthLimit.into())
}
+ pub fn burn_item_recursively(
+ from: T::CrossAccountId,
+ collection: CollectionId,
+ token: TokenId,
+ self_budget: &dyn Budget,
+ breadth_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ let handle = <CollectionHandle<T>>::try_get(collection)?;
+ let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = dispatch.as_dyn();
+ dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
+ }
+
pub fn check_nesting(
from: T::CrossAccountId,
under: &T::CrossAccountId,
pallets/unique/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/unique/src/benchmarking.rs
+++ b/pallets/unique/src/benchmarking.rs
@@ -22,7 +22,10 @@
use frame_support::traits::{tokens::currency::Currency, Get};
use frame_benchmarking::{benchmarks, account};
use sp_runtime::DispatchError;
-use pallet_common::benchmarking::{create_data, create_var_data, create_u16_data};
+use pallet_common::{
+ Config as CommonConfig,
+ benchmarking::{create_data, create_u16_data},
+};
const SEED: u32 = 1;
@@ -30,7 +33,7 @@
owner: T::AccountId,
mode: CollectionMode,
) -> Result<CollectionId, DispatchError> {
- T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
+ <T as CommonConfig>::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
let col_name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
let col_desc = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
@@ -54,7 +57,7 @@
let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
let mode: CollectionMode = CollectionMode::NFT;
let caller: T::AccountId = account("caller", 0, SEED);
- T::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
+ <T as CommonConfig>::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
}: _(RawOrigin::Signed(caller.clone()), col_name.clone(), col_desc.clone(), token_prefix.clone(), mode)
verify {
assert_eq!(<pallet_common::CollectionById<T>>::get(CollectionId(1)).unwrap().owner, caller);
@@ -77,16 +80,6 @@
let collection = create_nft_collection::<T>(caller.clone())?;
<Pallet<T>>::add_to_allow_list(RawOrigin::Signed(caller.clone()).into(), collection, T::CrossAccountId::from_sub(allowlist_account.clone()))?;
}: _(RawOrigin::Signed(caller.clone()), collection, T::CrossAccountId::from_sub(allowlist_account))
-
- set_public_access_mode {
- let caller: T::AccountId = account("caller", 0, SEED);
- let collection = create_nft_collection::<T>(caller.clone())?;
- }: _(RawOrigin::Signed(caller.clone()), collection, AccessMode::AllowList)
-
- set_mint_permission {
- let caller: T::AccountId = account("caller", 0, SEED);
- let collection = create_nft_collection::<T>(caller.clone())?;
- }: _(RawOrigin::Signed(caller.clone()), collection, true)
change_collection_owner {
let caller: T::AccountId = account("caller", 0, SEED);
@@ -145,7 +138,6 @@
owner_can_transfer: Some(true),
sponsored_data_rate_limit: None,
transfers_enabled: Some(true),
- nesting_rule: None,
};
}: set_collection_limits(RawOrigin::Signed(caller.clone()), collection, cl)
}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -469,6 +469,8 @@
#[derivative(Debug(format_with = "bounded::set_debug"))]
BoundedBTreeSet<CollectionId, ConstU32<16>>,
),
+ /// Used for tests
+ Permissive,
}
#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
runtime/common/src/weights.rsdiffbeforeafterboth--- a/runtime/common/src/weights.rs
+++ b/runtime/common/src/weights.rs
@@ -89,4 +89,12 @@
fn burn_from() -> Weight {
dispatch_weight::<T>() + max_weight_of!(burn_from())
}
+
+ fn burn_recursively_self_raw() -> Weight {
+ max_weight_of!(burn_recursively_self_raw())
+ }
+
+ fn burn_recursively_breadth_raw(amount: u32) -> Weight {
+ max_weight_of!(burn_recursively_breadth_raw(amount))
+ }
}
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -97,7 +97,8 @@
use up_data_structs::{
CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
CollectionStats, RpcCollection,
- mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}
+ mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
+ TokenChild,
};
use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
use xcm_builder::{