diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index f442e7b7d04ea01002e56abb20a84c2bb425f4ac..f9aeeace11fe743f106f4de1319c88e232485cb9 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -356,6 +356,8 @@ pub mod pallet { PathError, /// The provided path must consists of unique assets. NonUniquePath, + /// It was not possible to get or increment the Id of the pool. + IncorrectPoolAssetId, /// Unable to find an element in an array/vec that should have one-to-one correspondence /// with another. For example, an array of assets constituting a `path` should have a /// corresponding array of `amounts` along the path. @@ -426,8 +428,10 @@ pub mod pallet { MultiAssetIdConversionResult::Native => (), } - let lp_token = NextPoolAssetId::<T>::get().unwrap_or(T::PoolAssetId::initial_value()); - let next_lp_token_id = lp_token.increment(); + let lp_token = NextPoolAssetId::<T>::get() + .or(T::PoolAssetId::initial_value()) + .ok_or(Error::<T>::IncorrectPoolAssetId)?; + let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?; NextPoolAssetId::<T>::set(Some(next_lp_token_id)); T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?; @@ -1223,7 +1227,9 @@ pub mod pallet { /// Returns the next pool asset id for benchmark purposes only. #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn get_next_pool_asset_id() -> T::PoolAssetId { - NextPoolAssetId::<T>::get().unwrap_or(T::PoolAssetId::initial_value()) + NextPoolAssetId::<T>::get() + .or(T::PoolAssetId::initial_value()) + .expect("Next pool asset ID can not be None") } } } diff --git a/substrate/frame/nfts/src/benchmarking.rs b/substrate/frame/nfts/src/benchmarking.rs index 67ba29266245a759e5d39058176aee65b69dea33..995c842036746c86ca9ab3441a72fe4a52f2a820 100644 --- a/substrate/frame/nfts/src/benchmarking.rs +++ b/substrate/frame/nfts/src/benchmarking.rs @@ -247,7 +247,7 @@ benchmarks_instance_pallet! { let call = Call::<T, I>::create { admin, config: default_collection_config::<T, I>() }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::<T, I>(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); + assert_last_event::<T, I>(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into()); } force_create { @@ -255,7 +255,7 @@ benchmarks_instance_pallet! { let caller_lookup = T::Lookup::unlookup(caller.clone()); }: _(SystemOrigin::Root, caller_lookup, default_collection_config::<T, I>()) verify { - assert_last_event::<T, I>(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); + assert_last_event::<T, I>(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into()); } destroy { diff --git a/substrate/frame/nfts/src/common_functions.rs b/substrate/frame/nfts/src/common_functions.rs index a3486edec23cbcee6ca3c76b8490e95eecd154f1..b3dcef4cf324c128034a9a3a1ddf0e19e4659857 100644 --- a/substrate/frame/nfts/src/common_functions.rs +++ b/substrate/frame/nfts/src/common_functions.rs @@ -55,6 +55,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { Ok(()) } + pub(crate) fn set_next_collection_id(collection: T::CollectionId) { + let next_id = collection.increment(); + NextCollectionId::<T, I>::set(next_id); + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + } + #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_next_id(id: T::CollectionId) { NextCollectionId::<T, I>::set(Some(id)); @@ -62,6 +68,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { #[cfg(test)] pub fn get_next_id() -> T::CollectionId { - NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value()) + NextCollectionId::<T, I>::get() + .or(T::CollectionId::initial_value()) + .expect("Failed to get next collection ID") } } diff --git a/substrate/frame/nfts/src/features/create_delete_collection.rs b/substrate/frame/nfts/src/features/create_delete_collection.rs index e9434760628ec759774b7d57bc0ddaaba232d29f..aadad58689b43cb8dcbc9d590183b18caabd71f7 100644 --- a/substrate/frame/nfts/src/features/create_delete_collection.rs +++ b/substrate/frame/nfts/src/features/create_delete_collection.rs @@ -50,13 +50,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { ), ); - let next_id = collection.increment(); - CollectionConfigOf::<T, I>::insert(&collection, config); CollectionAccount::<T, I>::insert(&owner, &collection, ()); - NextCollectionId::<T, I>::set(Some(next_id)); - - Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); Self::deposit_event(event); Ok(()) } diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs index 090a6a372c1eb12c752714de48dd8bb0aa2b99f3..4e2593b4057d7b59510fc336589d7af77994566c 100644 --- a/substrate/frame/nfts/src/impl_nonfungibles.rs +++ b/substrate/frame/nfts/src/impl_nonfungibles.rs @@ -162,8 +162,9 @@ impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId, Collection Error::<T, I>::WrongSetting ); - let collection = - NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value()); + let collection = NextCollectionId::<T, I>::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::<T, I>::UnknownCollection)?; Self::do_create_collection( collection, @@ -173,8 +174,40 @@ impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId, Collection T::CollectionDeposit::get(), Event::Created { collection, creator: who.clone(), owner: admin.clone() }, )?; + + Self::set_next_collection_id(collection); + Ok(collection) } + + /// Create a collection of nonfungible items with `collection` Id to be owned by `who` and + /// managed by `admin`. Should be only used for applications that do not have an + /// incremental order for the collection IDs and is a replacement for the auto id creation. + /// + /// + /// SAFETY: This function can break the pallet if it is used in combination with the auto + /// increment functionality, as it can claim a value in the ID sequence. + fn create_collection_with_id( + collection: T::CollectionId, + who: &T::AccountId, + admin: &T::AccountId, + config: &CollectionConfigFor<T, I>, + ) -> Result<(), DispatchError> { + // DepositRequired can be disabled by calling the force_create() only + ensure!( + !config.has_disabled_setting(CollectionSetting::DepositRequired), + Error::<T, I>::WrongSetting + ); + + Self::do_create_collection( + collection, + who.clone(), + admin.clone(), + *config, + T::CollectionDeposit::get(), + Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + ) + } } impl<T: Config<I>, I: 'static> Destroy<<T as SystemConfig>::AccountId> for Pallet<T, I> { diff --git a/substrate/frame/nfts/src/lib.rs b/substrate/frame/nfts/src/lib.rs index 8124c71682451e9ae01dafd5c8182d27f91d2a70..e1325d4cbb04dc01847acd2b28e1e9a40d7ee844 100644 --- a/substrate/frame/nfts/src/lib.rs +++ b/substrate/frame/nfts/src/lib.rs @@ -101,6 +101,14 @@ pub mod pallet { + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// Identifier for the collection of item. + /// + /// SAFETY: The functions in the `Incrementable` trait are fallible. If the functions + /// of the implementation both return `None`, the automatic CollectionId generation + /// should not be used. So the `create` and `force_create` extrinsics and the + /// `create_collection` function will return an `UnknownCollection` Error. Instead use + /// the `create_collection_with_id` function. However, if the `Incrementable` trait + /// implementation has an incremental order, the `create_collection_with_id` function + /// should not be used as it can claim a value in the ID sequence. type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; /// The type used to identify a unique item within a collection. @@ -476,7 +484,7 @@ pub mod pallet { /// Mint settings for a collection had changed. CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emitted when the `NextCollectionId` gets incremented. - NextCollectionIdIncremented { next_id: T::CollectionId }, + NextCollectionIdIncremented { next_id: Option<T::CollectionId> }, /// The price was set for the item. ItemPriceSet { collection: T::CollectionId, @@ -665,8 +673,9 @@ pub mod pallet { admin: AccountIdLookupOf<T>, config: CollectionConfigFor<T, I>, ) -> DispatchResult { - let collection = - NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value()); + let collection = NextCollectionId::<T, I>::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::<T, I>::UnknownCollection)?; let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; @@ -684,7 +693,10 @@ pub mod pallet { config, T::CollectionDeposit::get(), Event::Created { collection, creator: owner, owner: admin }, - ) + )?; + + Self::set_next_collection_id(collection); + Ok(()) } /// Issue a new collection of non-fungible items from a privileged origin. @@ -712,8 +724,9 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; - let collection = - NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value()); + let collection = NextCollectionId::<T, I>::get() + .or(T::CollectionId::initial_value()) + .ok_or(Error::<T, I>::UnknownCollection)?; Self::do_create_collection( collection, @@ -722,7 +735,10 @@ pub mod pallet { config, Zero::zero(), Event::ForceCreated { collection, owner }, - ) + )?; + + Self::set_next_collection_id(collection); + Ok(()) } /// Destroy a collection of fungible items. diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index 4df57cb13218f3b5a6bcaeafe048788800326c1a..c94e75d3439309ca11ec002396d7f7e64e3e31ca 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, dispatch::Dispatchable, traits::{ - tokens::nonfungibles_v2::{Destroy, Mutate}, + tokens::nonfungibles_v2::{Create, Destroy, Mutate}, Currency, Get, }, }; @@ -3678,3 +3678,41 @@ fn pre_signed_attributes_should_work() { ); }) } + +#[test] +fn basic_create_collection_with_id_should_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Nfts::create_collection_with_id( + 0u32, + &account(1), + &account(1), + &default_collection_config(), + ), + Error::<Test>::WrongSetting + ); + + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + + assert_ok!(Nfts::create_collection_with_id( + 0u32, + &account(1), + &account(1), + &collection_config_with_all_settings_enabled(), + )); + + assert_eq!(collections(), vec![(account(1), 0)]); + + // CollectionId already taken. + assert_noop!( + Nfts::create_collection_with_id( + 0u32, + &account(2), + &account(2), + &collection_config_with_all_settings_enabled(), + ), + Error::<Test>::CollectionIdInUse + ); + }); +} diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 5947be57ae1c6bb6902d645c4b465d8110aab52a..829cd31e4c3703655dd65700dc643d85ac378026 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -132,24 +132,37 @@ macro_rules! impl_incrementable { ($($type:ty),+) => { $( impl Incrementable for $type { - fn increment(&self) -> Self { + fn increment(&self) -> Option<Self> { let mut val = self.clone(); val.saturating_inc(); - val + Some(val) } - fn initial_value() -> Self { - 0 + fn initial_value() -> Option<Self> { + Some(0) } } )+ }; } -/// For example: allows new identifiers to be created in a linear fashion. -pub trait Incrementable { - fn increment(&self) -> Self; - fn initial_value() -> Self; +/// A trait representing an incrementable type. +/// +/// The `increment` and `initial_value` functions are fallible. +/// They should either both return `Some` with a valid value, or `None`. +pub trait Incrementable +where + Self: Sized, +{ + /// Increments the value. + /// + /// Returns `Some` with the incremented value if it is possible, or `None` if it is not. + fn increment(&self) -> Option<Self>; + + /// Returns the initial value. + /// + /// Returns `Some` with the initial value if it is available, or `None` if it is not. + fn initial_value() -> Option<Self>; } impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs index 6f428c297e73cfc58cb2fed40f14e7102f19886b..345cce237b67b1955b14d2090f5a6ce07039f86c 100644 --- a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -198,6 +198,13 @@ pub trait Create<AccountId, CollectionConfig>: Inspect<AccountId> { admin: &AccountId, config: &CollectionConfig, ) -> Result<Self::CollectionId, DispatchError>; + + fn create_collection_with_id( + collection: Self::CollectionId, + who: &AccountId, + admin: &AccountId, + config: &CollectionConfig, + ) -> Result<(), DispatchError>; } /// Trait for providing the ability to destroy collections of nonfungible items.