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// 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::{