Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
[Uniques V2] Minting options (#12483)
Browse files Browse the repository at this point in the history
* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Change the format of CollectionConfig to store more data

* Move max supply to the CollectionConfig and allow to change it

* Remove ItemConfig from the mint() function and use the one set in mint settings

* Add different mint options

* Allow to change the mint settings

* Add a force_mint() method

* Check mint params

* Some optimisations

* Cover with tests

* Remove merge artifacts

* Chore

* Use the new has_role() method

* Rework item deposits

* More tests

* Refactoring

* Address comments

* Refactor lock_collection()

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Private => Issuer

* Add more tests

* Fix benchmarks

* Add benchmarks for new methods

* [Uniques v2] Refactoring (#12570)

* Move do_set_price() and do_buy_item() to buy_sell.rs

* Move approvals to feature file

* Move metadata to feature files

* Move the rest of methods to feature files

* Remove artifacts

* Split force_collection_status into 2 methods

* Fix benchmarks

* Fix benchmarks

* Update deps

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>
  • Loading branch information
jsidorenko and gilescope authored Nov 16, 2022
1 parent 1813960 commit afd4c18
Show file tree
Hide file tree
Showing 22 changed files with 1,856 additions and 1,198 deletions.
10 changes: 5 additions & 5 deletions frame/nfts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive"
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" }

[dev-dependencies]
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
sp-core = { version = "6.0.0", path = "../../primitives/core" }
sp-io = { version = "6.0.0", path = "../../primitives/io" }
sp-std = { version = "4.0.0", path = "../../primitives/std" }
sp-core = { version = "7.0.0", path = "../../primitives/core" }
sp-io = { version = "7.0.0", path = "../../primitives/io" }
sp-std = { version = "5.0.0", path = "../../primitives/std" }

[features]
default = ["std"]
Expand Down
96 changes: 73 additions & 23 deletions frame/nfts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#![cfg(feature = "runtime-benchmarks")]

use super::*;
use enumflags2::{BitFlag, BitFlags};
use frame_benchmarking::{
account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller,
};
Expand All @@ -46,7 +47,7 @@ fn create_collection<T: Config<I>, I: 'static>(
assert_ok!(Nfts::<T, I>::force_create(
SystemOrigin::Root.into(),
caller_lookup.clone(),
CollectionConfig::all_settings_enabled()
default_collection_config::<T, I>()
));
(collection, caller, caller_lookup)
}
Expand Down Expand Up @@ -78,8 +79,7 @@ fn mint_item<T: Config<I>, I: 'static>(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
caller_lookup.clone(),
ItemConfig::all_settings_enabled(),
None,
));
(item, caller, caller_lookup)
}
Expand Down Expand Up @@ -128,6 +128,24 @@ fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::
assert_eq!(event, &system_event);
}

fn make_collection_config<T: Config<I>, I: 'static>(
disable_settings: BitFlags<CollectionSetting>,
) -> CollectionConfigFor<T, I> {
CollectionConfig {
settings: CollectionSettings::from_disabled(disable_settings),
max_supply: None,
mint_settings: MintSettings::default(),
}
}

fn default_collection_config<T: Config<I>, I: 'static>() -> CollectionConfigFor<T, I> {
make_collection_config::<T, I>(CollectionSetting::empty())
}

fn default_item_config() -> ItemConfig {
ItemConfig { settings: ItemSettings::all_enabled() }
}

benchmarks_instance_pallet! {
create {
let collection = T::Helper::collection(0);
Expand All @@ -136,7 +154,7 @@ benchmarks_instance_pallet! {
whitelist_account!(caller);
let admin = T::Lookup::unlookup(caller.clone());
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let call = Call::<T, I>::create { admin, config: CollectionConfig::all_settings_enabled() };
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());
Expand All @@ -145,7 +163,7 @@ benchmarks_instance_pallet! {
force_create {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
}: _(SystemOrigin::Root, caller_lookup, CollectionConfig::all_settings_enabled())
}: _(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());
}
Expand All @@ -169,7 +187,15 @@ benchmarks_instance_pallet! {
mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, ItemConfig::all_settings_enabled())
}: _(SystemOrigin::Signed(caller.clone()), collection, item, None)
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}

force_mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config())
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}
Expand All @@ -188,6 +214,7 @@ benchmarks_instance_pallet! {

let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
}: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup)
verify {
assert_last_event::<T, I>(Event::Transferred { collection, item, from: caller, to: target }.into());
Expand All @@ -197,14 +224,10 @@ benchmarks_instance_pallet! {
let i in 0 .. 5_000;
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let items = (0..i).map(|x| mint_item::<T, I>(x as u16).0).collect::<Vec<_>>();
Nfts::<T, I>::force_collection_status(
Nfts::<T, I>::force_collection_config(
SystemOrigin::Root.into(),
collection,
caller_lookup.clone(),
caller_lookup.clone(),
caller_lookup.clone(),
caller_lookup,
CollectionConfig(CollectionSetting::DepositRequired.into()),
make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, items.clone())
verify {
Expand Down Expand Up @@ -234,12 +257,13 @@ benchmarks_instance_pallet! {

lock_collection {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let lock_config = CollectionConfig(
let lock_settings = CollectionSettings::from_disabled(
CollectionSetting::TransferableItems |
CollectionSetting::UnlockedMetadata |
CollectionSetting::UnlockedAttributes,
CollectionSetting::UnlockedAttributes |
CollectionSetting::UnlockedMaxSupply,
);
}: _(SystemOrigin::Signed(caller.clone()), collection, lock_config)
}: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings)
verify {
assert_last_event::<T, I>(Event::CollectionLocked { collection }.into());
}
Expand Down Expand Up @@ -271,20 +295,31 @@ benchmarks_instance_pallet! {
}.into());
}

force_collection_status {
force_collection_owner {
let (collection, _, _) = create_collection::<T, I>();
let origin = T::ForceOrigin::successful_origin();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let call = Call::<T, I>::force_collection_owner {
collection,
owner: target_lookup,
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::OwnerChanged { collection, new_owner: target }.into());
}

force_collection_config {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let origin = T::ForceOrigin::successful_origin();
let call = Call::<T, I>::force_collection_status {
let call = Call::<T, I>::force_collection_config {
collection,
owner: caller_lookup.clone(),
issuer: caller_lookup.clone(),
admin: caller_lookup.clone(),
freezer: caller_lookup,
config: CollectionConfig(CollectionSetting::DepositRequired.into()),
config: make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::CollectionStatusChanged { collection }.into());
assert_last_event::<T, I>(Event::CollectionConfigChanged { collection }.into());
}

lock_item_properties {
Expand Down Expand Up @@ -414,6 +449,20 @@ benchmarks_instance_pallet! {
}.into());
}

update_mint_settings {
let (collection, caller, _) = create_collection::<T, I>();
let mint_settings = MintSettings {
mint_type: MintType::HolderOf(T::Helper::collection(0)),
start_block: Some(One::one()),
end_block: Some(One::one()),
price: Some(ItemPrice::<T, I>::from(1u32)),
default_item_settings: ItemSettings::all_enabled(),
};
}: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings)
verify {
assert_last_event::<T, I>(Event::CollectionMintSettingsUpdated { collection }.into());
}

set_price {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
Expand Down Expand Up @@ -528,6 +577,7 @@ benchmarks_instance_pallet! {
let duration = T::MaxDeadlineDuration::get();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let origin = SystemOrigin::Signed(caller.clone());
frame_system::Pallet::<T>::set_block_number(One::one());
Nfts::<T, I>::transfer(origin.clone().into(), collection, item2, target_lookup)?;
Expand Down
42 changes: 42 additions & 0 deletions frame/nfts/src/common_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This file is part of Substrate.

// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Various pieces of common functionality.
use super::*;

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get the owner of the item, if the item exists.
pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
Item::<T, I>::get(collection, item).map(|i| i.owner)
}

/// Get the owner of the item, if the item exists.
pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
Collection::<T, I>::get(collection).map(|i| i.owner)
}

#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn set_next_id(id: T::CollectionId) {
NextCollectionId::<T, I>::set(Some(id));
}

#[cfg(test)]
pub fn get_next_id() -> T::CollectionId {
NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value())
}
}
132 changes: 132 additions & 0 deletions frame/nfts/src/features/approvals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// This file is part of Substrate.

// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::*;
use frame_support::pallet_prelude::*;

impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_approve_transfer(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
maybe_deadline: Option<<T as SystemConfig>::BlockNumber>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Approvals),
Error::<T, I>::MethodDisabled
);
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;

let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);

if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}

let now = frame_system::Pallet::<T>::block_number();
let deadline = maybe_deadline.map(|d| d.saturating_add(now));

details
.approvals
.try_insert(delegate.clone(), deadline)
.map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
Item::<T, I>::insert(&collection, &item, &details);

Self::deposit_event(Event::ApprovedTransfer {
collection,
item,
owner: details.owner,
delegate,
deadline,
});

Ok(())
}

pub(crate) fn do_cancel_approval(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;

let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::<T, I>::NotDelegate)?;

let is_past_deadline = if let Some(deadline) = maybe_deadline {
let now = frame_system::Pallet::<T>::block_number();
now > *deadline
} else {
false
};

if !is_past_deadline {
if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}
}

details.approvals.remove(&delegate);
Item::<T, I>::insert(&collection, &item, &details);

Self::deposit_event(Event::ApprovalCancelled {
collection,
item,
owner: details.owner,
delegate,
});

Ok(())
}

pub(crate) fn do_clear_all_transfer_approvals(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;

if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}

details.approvals.clear();
Item::<T, I>::insert(&collection, &item, &details);

Self::deposit_event(Event::AllApprovalsCancelled {
collection,
item,
owner: details.owner,
});

Ok(())
}
}
Loading

0 comments on commit afd4c18

Please sign in to comment.