mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 06:17:56 +00:00
[NFTs] Rework permissions model (#13482)
* Disallow admin to transfer or burn items he doesn't own * lock_collection should be accessible by collection's owner only * Allow admin to access lock_item_properties() * Fix do_lock_item_properties * Move update_mint_settings() to Issuer * Rename check_owner to check_origin * Typo * Make admin to be in charge of managing the metadata * Make admin the main attributes manager * offchain mint should be signed by Issuer * Remove the special case when the Issuer calls the mint() function * Rework burn and destroy methods * Return back item_metadatas * Don't repatriate the deposit on transfer * A bit more tests * One more test * Add migration * Chore * Clippy * Rename to owned_item * Address comments * Replace .filter_map with .find_map * Improve version validation in pre_upgrade() * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts --------- Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -72,6 +72,40 @@ fn add_collection_metadata<T: Config<I>, I: 'static>() -> (T::AccountId, Account
|
||||
|
||||
fn mint_item<T: Config<I>, I: 'static>(
|
||||
index: u16,
|
||||
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let item = T::Helper::item(index);
|
||||
let collection = T::Helper::collection(0);
|
||||
let caller = Collection::<T, I>::get(collection).unwrap().owner;
|
||||
if caller != whitelisted_caller() {
|
||||
whitelist_account!(caller);
|
||||
}
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let item_exists = Item::<T, I>::contains_key(&collection, &item);
|
||||
let item_config = ItemConfigOf::<T, I>::get(&collection, &item);
|
||||
if item_exists {
|
||||
return (item, caller, caller_lookup)
|
||||
} else if let Some(item_config) = item_config {
|
||||
assert_ok!(Nfts::<T, I>::force_mint(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
collection,
|
||||
item,
|
||||
caller_lookup.clone(),
|
||||
item_config,
|
||||
));
|
||||
} else {
|
||||
assert_ok!(Nfts::<T, I>::mint(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
collection,
|
||||
item,
|
||||
caller_lookup.clone(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
(item, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn lock_item<T: Config<I>, I: 'static>(
|
||||
index: u16,
|
||||
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
|
||||
if caller != whitelisted_caller() {
|
||||
@@ -79,12 +113,27 @@ fn mint_item<T: Config<I>, I: 'static>(
|
||||
}
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let item = T::Helper::item(index);
|
||||
assert_ok!(Nfts::<T, I>::mint(
|
||||
assert_ok!(Nfts::<T, I>::lock_item_transfer(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
T::Helper::collection(0),
|
||||
item,
|
||||
));
|
||||
(item, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn burn_item<T: Config<I>, I: 'static>(
|
||||
index: u16,
|
||||
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
|
||||
if caller != whitelisted_caller() {
|
||||
whitelist_account!(caller);
|
||||
}
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let item = T::Helper::item(index);
|
||||
assert_ok!(Nfts::<T, I>::burn(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
T::Helper::collection(0),
|
||||
item,
|
||||
caller_lookup.clone(),
|
||||
None,
|
||||
));
|
||||
(item, caller, caller_lookup)
|
||||
}
|
||||
@@ -126,6 +175,26 @@ fn add_item_attribute<T: Config<I>, I: 'static>(
|
||||
(key, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn add_collection_attribute<T: Config<I>, I: 'static>(
|
||||
i: u16,
|
||||
) -> (BoundedVec<u8, T::KeyLimit>, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
|
||||
if caller != whitelisted_caller() {
|
||||
whitelist_account!(caller);
|
||||
}
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let key: BoundedVec<_, _> = make_filled_vec(i, T::KeyLimit::get() as usize).try_into().unwrap();
|
||||
assert_ok!(Nfts::<T, I>::set_attribute(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
T::Helper::collection(0),
|
||||
None,
|
||||
AttributeNamespace::CollectionOwner,
|
||||
key.clone(),
|
||||
vec![0; T::ValueLimit::get() as usize].try_into().unwrap(),
|
||||
));
|
||||
(key, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
let events = frame_system::Pallet::<T>::events();
|
||||
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
|
||||
@@ -190,26 +259,25 @@ benchmarks_instance_pallet! {
|
||||
}
|
||||
|
||||
destroy {
|
||||
let n in 0 .. 1_000;
|
||||
let m in 0 .. 1_000;
|
||||
let c in 0 .. 1_000;
|
||||
let a in 0 .. 1_000;
|
||||
|
||||
let (collection, caller, _) = create_collection::<T, I>();
|
||||
add_collection_metadata::<T, I>();
|
||||
for i in 0..n {
|
||||
mint_item::<T, I>(i as u16);
|
||||
}
|
||||
for i in 0..m {
|
||||
if !Item::<T, I>::contains_key(collection, T::Helper::item(i as u16)) {
|
||||
mint_item::<T, I>(i as u16);
|
||||
}
|
||||
mint_item::<T, I>(i as u16);
|
||||
add_item_metadata::<T, I>(T::Helper::item(i as u16));
|
||||
lock_item::<T, I>(i as u16);
|
||||
burn_item::<T, I>(i as u16);
|
||||
}
|
||||
for i in 0..c {
|
||||
mint_item::<T, I>(i as u16);
|
||||
lock_item::<T, I>(i as u16);
|
||||
burn_item::<T, I>(i as u16);
|
||||
}
|
||||
for i in 0..a {
|
||||
if !Item::<T, I>::contains_key(collection, T::Helper::item(i as u16)) {
|
||||
mint_item::<T, I>(i as u16);
|
||||
}
|
||||
add_item_attribute::<T, I>(T::Helper::item(i as u16));
|
||||
add_collection_attribute::<T, I>(i as u16);
|
||||
}
|
||||
let witness = Collection::<T, I>::get(collection).unwrap().destroy_witness();
|
||||
}: _(SystemOrigin::Signed(caller), collection, witness)
|
||||
@@ -234,9 +302,9 @@ benchmarks_instance_pallet! {
|
||||
}
|
||||
|
||||
burn {
|
||||
let (collection, caller, caller_lookup) = create_collection::<T, I>();
|
||||
let (collection, caller, _) = create_collection::<T, I>();
|
||||
let (item, ..) = mint_item::<T, I>(0);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup))
|
||||
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Burned { collection, item, owner: caller }.into());
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
);
|
||||
|
||||
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);
|
||||
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
@@ -85,9 +83,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
|
||||
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);
|
||||
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +109,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
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);
|
||||
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
|
||||
details.approvals.clear();
|
||||
|
||||
@@ -33,17 +33,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Error::<T, I>::MethodDisabled
|
||||
);
|
||||
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
ensure!(
|
||||
Self::is_valid_namespace(
|
||||
&origin,
|
||||
&namespace,
|
||||
&collection,
|
||||
&collection_details.owner,
|
||||
&maybe_item,
|
||||
)?,
|
||||
Self::is_valid_namespace(&origin, &namespace, &collection, &maybe_item)?,
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
|
||||
@@ -66,6 +57,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
|
||||
let attribute_exists = attribute.is_some();
|
||||
if !attribute_exists {
|
||||
@@ -219,7 +213,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_clear_attribute(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
maybe_item: Option<T::ItemId>,
|
||||
namespace: AttributeNamespace<T::AccountId>,
|
||||
@@ -227,21 +221,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
) -> DispatchResult {
|
||||
let (_, deposit) = Attribute::<T, I>::take((collection, maybe_item, &namespace, &key))
|
||||
.ok_or(Error::<T, I>::AttributeNotFound)?;
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
// validate the provided namespace when it's not a root call and the caller is not
|
||||
// the same as the `deposit.account` (e.g. the deposit was paid by different account)
|
||||
if deposit.account != maybe_check_owner {
|
||||
if deposit.account != maybe_check_origin {
|
||||
ensure!(
|
||||
Self::is_valid_namespace(
|
||||
&check_owner,
|
||||
&namespace,
|
||||
&collection,
|
||||
&collection_details.owner,
|
||||
&maybe_item,
|
||||
)?,
|
||||
Self::is_valid_namespace(&check_origin, &namespace, &collection, &maybe_item)?,
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
@@ -264,17 +250,15 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
.map_or(None, |c| {
|
||||
Some(c.has_disabled_setting(ItemSetting::UnlockedAttributes))
|
||||
});
|
||||
match maybe_is_locked {
|
||||
Some(is_locked) => {
|
||||
// when item exists, then only the collection's owner can clear that
|
||||
// attribute
|
||||
ensure!(
|
||||
check_owner == &collection_details.owner,
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
ensure!(!is_locked, Error::<T, I>::LockedItemAttributes);
|
||||
},
|
||||
None => (),
|
||||
if let Some(is_locked) = maybe_is_locked {
|
||||
ensure!(!is_locked, Error::<T, I>::LockedItemAttributes);
|
||||
// Only the collection's admin can clear attributes in that namespace.
|
||||
// e.g. in off-chain mints, the attribute's depositor will be the item's
|
||||
// owner, that's why we need to do this extra check.
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -282,6 +266,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
};
|
||||
}
|
||||
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
collection_details.attributes.saturating_dec();
|
||||
|
||||
match deposit.account {
|
||||
@@ -372,12 +359,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
origin: &T::AccountId,
|
||||
namespace: &AttributeNamespace<T::AccountId>,
|
||||
collection: &T::CollectionId,
|
||||
collection_owner: &T::AccountId,
|
||||
maybe_item: &Option<T::ItemId>,
|
||||
) -> Result<bool, DispatchError> {
|
||||
let mut result = false;
|
||||
match namespace {
|
||||
AttributeNamespace::CollectionOwner => result = origin == collection_owner,
|
||||
AttributeNamespace::CollectionOwner =>
|
||||
result = Self::has_role(&collection, &origin, CollectionRole::Admin),
|
||||
AttributeNamespace::ItemOwner =>
|
||||
if let Some(item) = maybe_item {
|
||||
let item_details =
|
||||
|
||||
@@ -38,6 +38,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
owner_deposit: deposit,
|
||||
items: 0,
|
||||
item_metadatas: 0,
|
||||
item_configs: 0,
|
||||
attributes: 0,
|
||||
},
|
||||
);
|
||||
@@ -71,24 +72,23 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
if let Some(check_owner) = maybe_check_owner {
|
||||
ensure!(collection_details.owner == check_owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
ensure!(collection_details.items == witness.items, Error::<T, I>::BadWitness);
|
||||
ensure!(collection_details.items == 0, Error::<T, I>::CollectionNotEmpty);
|
||||
ensure!(collection_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
|
||||
ensure!(
|
||||
collection_details.item_metadatas == witness.item_metadatas,
|
||||
Error::<T, I>::BadWitness
|
||||
);
|
||||
ensure!(collection_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
|
||||
ensure!(
|
||||
collection_details.item_configs == witness.item_configs,
|
||||
Error::<T, I>::BadWitness
|
||||
);
|
||||
|
||||
for (item, details) in Item::<T, I>::drain_prefix(&collection) {
|
||||
Account::<T, I>::remove((&details.owner, &collection, &item));
|
||||
T::Currency::unreserve(&details.deposit.account, details.deposit.amount);
|
||||
}
|
||||
for (_, metadata) in ItemMetadataOf::<T, I>::drain_prefix(&collection) {
|
||||
if let Some(depositor) = metadata.deposit.account {
|
||||
T::Currency::unreserve(&depositor, metadata.deposit.amount);
|
||||
}
|
||||
}
|
||||
let _ = ItemPriceOf::<T, I>::clear_prefix(&collection, witness.items, None);
|
||||
let _ = PendingSwapOf::<T, I>::clear_prefix(&collection, witness.items, None);
|
||||
|
||||
CollectionMetadataOf::<T, I>::remove(&collection);
|
||||
Self::clear_roles(&collection)?;
|
||||
|
||||
@@ -103,15 +103,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
CollectionAccount::<T, I>::remove(&collection_details.owner, &collection);
|
||||
T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit);
|
||||
CollectionConfigOf::<T, I>::remove(&collection);
|
||||
let _ = ItemConfigOf::<T, I>::clear_prefix(&collection, witness.items, None);
|
||||
let _ =
|
||||
ItemAttributesApprovalsOf::<T, I>::clear_prefix(&collection, witness.items, None);
|
||||
let _ = ItemConfigOf::<T, I>::clear_prefix(&collection, witness.item_configs, None);
|
||||
|
||||
Self::deposit_event(Event::Destroyed { collection });
|
||||
|
||||
Ok(DestroyWitness {
|
||||
items: collection_details.items,
|
||||
item_metadatas: collection_details.item_metadatas,
|
||||
item_configs: collection_details.item_configs,
|
||||
attributes: collection_details.attributes,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,6 +66,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
ensure!(existing_config == item_config, Error::<T, I>::InconsistentItemConfig);
|
||||
} else {
|
||||
ItemConfigOf::<T, I>::insert(&collection, &item, item_config);
|
||||
collection_details.item_configs.saturating_inc();
|
||||
}
|
||||
|
||||
T::Currency::reserve(&deposit_account, deposit_amount)?;
|
||||
@@ -107,7 +108,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
|
||||
let collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
ensure!(collection_details.owner == signer, Error::<T, I>::NoPermission);
|
||||
|
||||
ensure!(
|
||||
Self::has_role(&collection, &signer, CollectionRole::Issuer),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
|
||||
let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? };
|
||||
Self::do_mint(
|
||||
@@ -118,9 +123,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
item_config,
|
||||
|_, _| Ok(()),
|
||||
)?;
|
||||
let origin = Self::find_account_by_role(&collection, CollectionRole::Admin)
|
||||
.unwrap_or(collection_details.owner.clone());
|
||||
for (key, value) in attributes {
|
||||
Self::do_set_attribute(
|
||||
collection_details.owner.clone(),
|
||||
origin.clone(),
|
||||
collection,
|
||||
Some(item),
|
||||
AttributeNamespace::CollectionOwner,
|
||||
@@ -131,7 +138,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
if !metadata.len().is_zero() {
|
||||
Self::do_set_item_metadata(
|
||||
Some(collection_details.owner.clone()),
|
||||
Some(origin.clone()),
|
||||
collection,
|
||||
item,
|
||||
metadata,
|
||||
@@ -148,6 +155,9 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
) -> DispatchResult {
|
||||
ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
|
||||
let item_config = Self::get_item_config(&collection, &item)?;
|
||||
// NOTE: if item's settings are not empty (e.g. item's metadata is locked)
|
||||
// then we keep the config record and don't remove it
|
||||
let remove_config = !item_config.has_disabled_settings();
|
||||
let owner = Collection::<T, I>::try_mutate(
|
||||
&collection,
|
||||
|maybe_collection_details| -> Result<T::AccountId, DispatchError> {
|
||||
@@ -161,6 +171,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
T::Currency::unreserve(&details.deposit.account, details.deposit.amount);
|
||||
collection_details.items.saturating_dec();
|
||||
|
||||
if remove_config {
|
||||
collection_details.item_configs.saturating_dec();
|
||||
}
|
||||
|
||||
// Clear the metadata if it's not locked.
|
||||
if item_config.is_setting_enabled(ItemSetting::UnlockedMetadata) {
|
||||
if let Some(metadata) = ItemMetadataOf::<T, I>::take(&collection, &item) {
|
||||
@@ -188,9 +202,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
PendingSwapOf::<T, I>::remove(&collection, &item);
|
||||
ItemAttributesApprovalsOf::<T, I>::remove(&collection, &item);
|
||||
|
||||
// NOTE: if item's settings are not empty (e.g. item's metadata is locked)
|
||||
// then we keep the record and don't remove it
|
||||
if !item_config.has_disabled_settings() {
|
||||
if remove_config {
|
||||
ItemConfigOf::<T, I>::remove(&collection, &item);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
collection: T::CollectionId,
|
||||
lock_settings: CollectionSettings,
|
||||
) -> DispatchResult {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &origin, CollectionRole::Freezer),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
ensure!(Self::collection_owner(collection) == Some(origin), Error::<T, I>::NoPermission);
|
||||
ensure!(
|
||||
!lock_settings.is_disabled(CollectionSetting::DepositRequired),
|
||||
Error::<T, I>::WrongSetting
|
||||
@@ -85,17 +82,17 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_lock_item_properties(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
lock_metadata: bool,
|
||||
lock_attributes: bool,
|
||||
) -> DispatchResult {
|
||||
let collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
ItemConfigOf::<T, I>::try_mutate(collection, item, |maybe_config| {
|
||||
|
||||
@@ -21,13 +21,20 @@ use frame_support::pallet_prelude::*;
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Note: if `maybe_depositor` is None, that means the depositor will be a collection's owner
|
||||
pub(crate) fn do_set_item_metadata(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
maybe_depositor: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
let is_root = maybe_check_owner.is_none();
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
let is_root = maybe_check_origin.is_none();
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
@@ -37,10 +44,6 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Error::<T, I>::LockedItemMetadata
|
||||
);
|
||||
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
|
||||
let collection_config = Self::get_collection_config(&collection)?;
|
||||
|
||||
ItemMetadataOf::<T, I>::try_mutate_exists(collection, item, |metadata| {
|
||||
@@ -89,22 +92,26 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_clear_item_metadata(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
) -> DispatchResult {
|
||||
let is_root = maybe_check_owner.is_none();
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
let is_root = maybe_check_origin.is_none();
|
||||
let metadata = ItemMetadataOf::<T, I>::take(collection, item)
|
||||
.ok_or(Error::<T, I>::MetadataNotFound)?;
|
||||
let mut collection_details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
|
||||
let depositor_account =
|
||||
metadata.deposit.account.unwrap_or(collection_details.owner.clone());
|
||||
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
|
||||
// NOTE: if the item was previously burned, the ItemConfigOf record might not exist
|
||||
let is_locked = Self::get_item_config(&collection, &item)
|
||||
.map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata));
|
||||
@@ -125,29 +132,32 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_set_collection_metadata(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
) -> DispatchResult {
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
let is_root = maybe_check_origin.is_none();
|
||||
let collection_config = Self::get_collection_config(&collection)?;
|
||||
ensure!(
|
||||
maybe_check_owner.is_none() ||
|
||||
collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
|
||||
is_root || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
|
||||
Error::<T, I>::LockedCollectionMetadata
|
||||
);
|
||||
|
||||
let mut details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
|
||||
}
|
||||
|
||||
CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
|
||||
let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
|
||||
details.owner_deposit.saturating_reduce(old_deposit);
|
||||
let mut deposit = Zero::zero();
|
||||
if maybe_check_owner.is_some() &&
|
||||
collection_config.is_setting_enabled(CollectionSetting::DepositRequired)
|
||||
if !is_root && collection_config.is_setting_enabled(CollectionSetting::DepositRequired)
|
||||
{
|
||||
deposit = T::DepositPerByte::get()
|
||||
.saturating_mul(((data.len()) as u32).into())
|
||||
@@ -170,18 +180,22 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_clear_collection_metadata(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
) -> DispatchResult {
|
||||
let details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
let details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
let collection_config = Self::get_collection_config(&collection)?;
|
||||
|
||||
ensure!(
|
||||
maybe_check_owner.is_none() ||
|
||||
maybe_check_origin.is_none() ||
|
||||
collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
|
||||
Error::<T, I>::LockedCollectionMetadata
|
||||
);
|
||||
|
||||
@@ -82,6 +82,21 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
.map_or(false, |roles| roles.has_role(role))
|
||||
}
|
||||
|
||||
/// Finds the account by a provided role within a collection.
|
||||
///
|
||||
/// - `collection_id`: A collection to check the role in.
|
||||
/// - `role`: A role to find the account for.
|
||||
///
|
||||
/// Returns `Some(T::AccountId)` if the record was found, `None` otherwise.
|
||||
pub(crate) fn find_account_by_role(
|
||||
collection_id: &T::CollectionId,
|
||||
role: CollectionRole,
|
||||
) -> Option<T::AccountId> {
|
||||
CollectionRoleOf::<T, I>::iter_prefix(&collection_id).into_iter().find_map(
|
||||
|(account, roles)| if roles.has_role(role) { Some(account.clone()) } else { None },
|
||||
)
|
||||
}
|
||||
|
||||
/// Groups provided roles by account, given one account could have multiple roles.
|
||||
///
|
||||
/// - `input`: A vector of (Account, Role) tuples.
|
||||
|
||||
@@ -57,7 +57,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
pub(crate) fn do_update_mint_settings(
|
||||
maybe_check_owner: Option<T::AccountId>,
|
||||
maybe_check_origin: Option<T::AccountId>,
|
||||
collection: T::CollectionId,
|
||||
mint_settings: MintSettings<
|
||||
BalanceOf<T, I>,
|
||||
@@ -65,10 +65,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
T::CollectionId,
|
||||
>,
|
||||
) -> DispatchResult {
|
||||
let details =
|
||||
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
|
||||
if let Some(check_owner) = &maybe_check_owner {
|
||||
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
|
||||
if let Some(check_origin) = &maybe_check_origin {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &check_origin, CollectionRole::Issuer),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
}
|
||||
|
||||
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
|
||||
|
||||
@@ -48,16 +48,6 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
|
||||
with_details(&collection_details, &mut details)?;
|
||||
|
||||
if details.deposit.account == details.owner {
|
||||
// Move the deposit to the new owner.
|
||||
T::Currency::repatriate_reserved(
|
||||
&details.owner,
|
||||
&dest,
|
||||
details.deposit.amount,
|
||||
Reserved,
|
||||
)?;
|
||||
}
|
||||
|
||||
Account::<T, I>::remove((&details.owner, &collection, &item));
|
||||
Account::<T, I>::insert((&dest, &collection, &item), ());
|
||||
let origin = details.owner;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
#[cfg(test)]
|
||||
pub mod mock;
|
||||
#[cfg(test)]
|
||||
@@ -58,6 +59,9 @@ pub use pallet::*;
|
||||
pub use types::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// The log target of this pallet.
|
||||
pub const LOG_TARGET: &'static str = "runtime::nfts";
|
||||
|
||||
type AccountIdLookupOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
|
||||
|
||||
#[frame_support::pallet]
|
||||
@@ -67,9 +71,13 @@ pub mod pallet {
|
||||
use frame_system::pallet_prelude::*;
|
||||
use sp_runtime::traits::{IdentifyAccount, Verify};
|
||||
|
||||
/// The current storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<CollectionId, ItemId> {
|
||||
@@ -628,6 +636,8 @@ pub mod pallet {
|
||||
MaxAttributesLimitReached,
|
||||
/// The provided namespace isn't supported in this call.
|
||||
WrongNamespace,
|
||||
/// Can't delete non-empty collections.
|
||||
CollectionNotEmpty,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -719,20 +729,22 @@ pub mod pallet {
|
||||
/// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the
|
||||
/// owner of the `collection`.
|
||||
///
|
||||
/// NOTE: The collection must have 0 items to be destroyed.
|
||||
///
|
||||
/// - `collection`: The identifier of the collection to be destroyed.
|
||||
/// - `witness`: Information on the items minted in the collection. This must be
|
||||
/// correct.
|
||||
///
|
||||
/// Emits `Destroyed` event when successful.
|
||||
///
|
||||
/// Weight: `O(n + m)` where:
|
||||
/// - `n = witness.items`
|
||||
/// Weight: `O(m + c + a)` where:
|
||||
/// - `m = witness.item_metadatas`
|
||||
/// - `c = witness.item_configs`
|
||||
/// - `a = witness.attributes`
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::destroy(
|
||||
witness.items,
|
||||
witness.item_metadatas,
|
||||
witness.item_metadatas,
|
||||
witness.item_configs,
|
||||
witness.attributes,
|
||||
))]
|
||||
pub fn destroy(
|
||||
@@ -746,8 +758,8 @@ pub mod pallet {
|
||||
let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
|
||||
|
||||
Ok(Some(T::WeightInfo::destroy(
|
||||
details.items,
|
||||
details.item_metadatas,
|
||||
details.item_configs,
|
||||
details.attributes,
|
||||
))
|
||||
.into())
|
||||
@@ -755,7 +767,7 @@ pub mod pallet {
|
||||
|
||||
/// Mint an item of a particular collection.
|
||||
///
|
||||
/// The origin must be Signed and the sender must be the Issuer of the `collection`.
|
||||
/// The origin must be Signed and the sender must comply with the `mint_settings` rules.
|
||||
///
|
||||
/// - `collection`: The collection of the item to be minted.
|
||||
/// - `item`: An identifier of the new item.
|
||||
@@ -789,11 +801,6 @@ pub mod pallet {
|
||||
mint_to.clone(),
|
||||
item_config,
|
||||
|collection_details, collection_config| {
|
||||
// Issuer can mint regardless of mint settings
|
||||
if Self::has_role(&collection, &caller, CollectionRole::Issuer) {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let mint_settings = collection_config.mint_settings;
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
|
||||
@@ -805,7 +812,12 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
match mint_settings.mint_type {
|
||||
MintType::Issuer => return Err(Error::<T, I>::NoPermission.into()),
|
||||
MintType::Issuer => {
|
||||
ensure!(
|
||||
Self::has_role(&collection, &caller, CollectionRole::Issuer),
|
||||
Error::<T, I>::NoPermission
|
||||
);
|
||||
},
|
||||
MintType::HolderOf(collection_id) => {
|
||||
let MintWitness { owned_item } =
|
||||
witness_data.ok_or(Error::<T, I>::BadWitness)?;
|
||||
@@ -899,38 +911,30 @@ pub mod pallet {
|
||||
|
||||
/// Destroy a single item.
|
||||
///
|
||||
/// Origin must be Signed and the signing account must be either:
|
||||
/// - the Admin of the `collection`;
|
||||
/// - the Owner of the `item`;
|
||||
/// The origin must conform to `ForceOrigin` or must be Signed and the signing account must
|
||||
/// be the owner of the `item`.
|
||||
///
|
||||
/// - `collection`: The collection of the item to be burned.
|
||||
/// - `item`: The item to be burned.
|
||||
/// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the
|
||||
/// item is owned by this value.
|
||||
///
|
||||
/// Emits `Burned` with the actual amount burned.
|
||||
/// Emits `Burned`.
|
||||
///
|
||||
/// Weight: `O(1)`
|
||||
/// Modes: `check_owner.is_some()`.
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(T::WeightInfo::burn())]
|
||||
pub fn burn(
|
||||
origin: OriginFor<T>,
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
check_owner: Option<AccountIdLookupOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
|
||||
Self::do_burn(collection, item, |details| {
|
||||
let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin);
|
||||
let is_permitted = is_admin || details.owner == origin;
|
||||
ensure!(is_permitted, Error::<T, I>::NoPermission);
|
||||
ensure!(
|
||||
check_owner.map_or(true, |o| o == details.owner),
|
||||
Error::<T, I>::WrongOwner
|
||||
);
|
||||
if let Some(check_origin) = maybe_check_origin {
|
||||
ensure!(details.owner == check_origin, Error::<T, I>::NoPermission);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -938,7 +942,6 @@ pub mod pallet {
|
||||
/// Move an item from the sender account to another.
|
||||
///
|
||||
/// Origin must be Signed and the signing account must be either:
|
||||
/// - the Admin of the `collection`;
|
||||
/// - the Owner of the `item`;
|
||||
/// - the approved delegate for the `item` (in this case, the approval is reset).
|
||||
///
|
||||
@@ -962,8 +965,7 @@ pub mod pallet {
|
||||
let dest = T::Lookup::lookup(dest)?;
|
||||
|
||||
Self::do_transfer(collection, item, dest, |_, details| {
|
||||
let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin);
|
||||
if details.owner != origin && !is_admin {
|
||||
if details.owner != origin {
|
||||
let deadline =
|
||||
details.approvals.get(&origin).ok_or(Error::<T, I>::NoPermission)?;
|
||||
if let Some(d) = deadline {
|
||||
@@ -1086,12 +1088,13 @@ pub mod pallet {
|
||||
|
||||
/// Disallows specified settings for the whole collection.
|
||||
///
|
||||
/// Origin must be Signed and the sender should be the Freezer of the `collection`.
|
||||
/// Origin must be Signed and the sender should be the Owner of the `collection`.
|
||||
///
|
||||
/// - `collection`: The collection to be locked.
|
||||
/// - `lock_settings`: The settings to be locked.
|
||||
///
|
||||
/// Note: it's possible to only lock(set) the setting, but not to unset it.
|
||||
///
|
||||
/// Emits `CollectionLocked`.
|
||||
///
|
||||
/// Weight: `O(1)`
|
||||
@@ -1243,7 +1246,6 @@ pub mod pallet {
|
||||
///
|
||||
/// Origin must be either:
|
||||
/// - the `Force` origin;
|
||||
/// - `Signed` with the signer being the Admin of the `collection`;
|
||||
/// - `Signed` with the signer being the Owner of the `item`;
|
||||
///
|
||||
/// Arguments:
|
||||
@@ -1273,7 +1275,6 @@ pub mod pallet {
|
||||
///
|
||||
/// Origin must be either:
|
||||
/// - the `Force` origin;
|
||||
/// - `Signed` with the signer being the Admin of the `collection`;
|
||||
/// - `Signed` with the signer being the Owner of the `item`;
|
||||
///
|
||||
/// Arguments:
|
||||
@@ -1298,8 +1299,8 @@ pub mod pallet {
|
||||
|
||||
/// Disallows changing the metadata or attributes of the item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// `collection`.
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin
|
||||
/// of the `collection`.
|
||||
///
|
||||
/// - `collection`: The collection if the `item`.
|
||||
/// - `item`: An item to be locked.
|
||||
@@ -1307,8 +1308,8 @@ pub mod pallet {
|
||||
/// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace
|
||||
/// should be locked.
|
||||
///
|
||||
/// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace
|
||||
/// only. When the metadata or attributes are locked, it won't be possible the unlock them.
|
||||
/// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace only.
|
||||
/// When the metadata or attributes are locked, it won't be possible the unlock them.
|
||||
///
|
||||
/// Emits `ItemPropertiesLocked`.
|
||||
///
|
||||
@@ -1322,11 +1323,11 @@ pub mod pallet {
|
||||
lock_metadata: bool,
|
||||
lock_attributes: bool,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_lock_item_properties(
|
||||
maybe_check_owner,
|
||||
maybe_check_origin,
|
||||
collection,
|
||||
item,
|
||||
lock_metadata,
|
||||
@@ -1337,7 +1338,7 @@ pub mod pallet {
|
||||
/// Set an attribute for a collection or item.
|
||||
///
|
||||
/// Origin must be Signed and must conform to the namespace ruleset:
|
||||
/// - `CollectionOwner` namespace could be modified by the `collection` owner only;
|
||||
/// - `CollectionOwner` namespace could be modified by the `collection` Admin only;
|
||||
/// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item`
|
||||
/// should be set in that case;
|
||||
/// - `Account(AccountId)` namespace could be modified only when the `origin` was given a
|
||||
@@ -1367,15 +1368,12 @@ pub mod pallet {
|
||||
value: BoundedVec<u8, T::ValueLimit>,
|
||||
) -> DispatchResult {
|
||||
let origin = ensure_signed(origin)?;
|
||||
Self::do_set_attribute(
|
||||
origin.clone(),
|
||||
collection,
|
||||
maybe_item,
|
||||
namespace,
|
||||
key,
|
||||
value,
|
||||
origin,
|
||||
)
|
||||
let depositor = match namespace {
|
||||
AttributeNamespace::CollectionOwner =>
|
||||
Self::collection_owner(collection).ok_or(Error::<T, I>::UnknownCollection)?,
|
||||
_ => origin.clone(),
|
||||
};
|
||||
Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor)
|
||||
}
|
||||
|
||||
/// Force-set an attribute for a collection or item.
|
||||
@@ -1490,7 +1488,7 @@ pub mod pallet {
|
||||
|
||||
/// Set the metadata for an item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the
|
||||
/// `collection`.
|
||||
///
|
||||
/// If the origin is Signed, then funds of signer are reserved according to the formula:
|
||||
@@ -1512,15 +1510,15 @@ pub mod pallet {
|
||||
item: T::ItemId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_set_item_metadata(maybe_check_owner, collection, item, data, None)
|
||||
Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None)
|
||||
}
|
||||
|
||||
/// Clear the metadata for an item.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the
|
||||
/// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the
|
||||
/// `collection`.
|
||||
///
|
||||
/// Any deposit is freed for the collection's owner.
|
||||
@@ -1538,15 +1536,15 @@ pub mod pallet {
|
||||
collection: T::CollectionId,
|
||||
item: T::ItemId,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_clear_item_metadata(maybe_check_owner, collection, item)
|
||||
Self::do_clear_item_metadata(maybe_check_origin, collection, item)
|
||||
}
|
||||
|
||||
/// Set the metadata for a collection.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of
|
||||
/// the `collection`.
|
||||
///
|
||||
/// If the origin is `Signed`, then funds of signer are reserved according to the formula:
|
||||
@@ -1566,15 +1564,15 @@ pub mod pallet {
|
||||
collection: T::CollectionId,
|
||||
data: BoundedVec<u8, T::StringLimit>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_set_collection_metadata(maybe_check_owner, collection, data)
|
||||
Self::do_set_collection_metadata(maybe_check_origin, collection, data)
|
||||
}
|
||||
|
||||
/// Clear the metadata for a collection.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of
|
||||
/// the `collection`.
|
||||
///
|
||||
/// Any deposit is freed for the collection's owner.
|
||||
@@ -1590,10 +1588,10 @@ pub mod pallet {
|
||||
origin: OriginFor<T>,
|
||||
collection: T::CollectionId,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_clear_collection_metadata(maybe_check_owner, collection)
|
||||
Self::do_clear_collection_metadata(maybe_check_origin, collection)
|
||||
}
|
||||
|
||||
/// Set (or reset) the acceptance of ownership for a particular account.
|
||||
@@ -1640,8 +1638,8 @@ pub mod pallet {
|
||||
|
||||
/// Update mint settings.
|
||||
///
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
|
||||
/// the `collection`.
|
||||
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Issuer
|
||||
/// of the `collection`.
|
||||
///
|
||||
/// - `collection`: The identifier of the collection to change.
|
||||
/// - `mint_settings`: The new mint settings.
|
||||
@@ -1658,15 +1656,15 @@ pub mod pallet {
|
||||
T::CollectionId,
|
||||
>,
|
||||
) -> DispatchResult {
|
||||
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
|
||||
let maybe_check_origin = T::ForceOrigin::try_origin(origin)
|
||||
.map(|_| None)
|
||||
.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
|
||||
Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings)
|
||||
Self::do_update_mint_settings(maybe_check_origin, collection, mint_settings)
|
||||
}
|
||||
|
||||
/// Set (or reset) the price for an item.
|
||||
///
|
||||
/// Origin must be Signed and must be the owner of the asset `item`.
|
||||
/// Origin must be Signed and must be the owner of the `item`.
|
||||
///
|
||||
/// - `collection`: The collection of the item.
|
||||
/// - `item`: The item to set the price for.
|
||||
@@ -1827,7 +1825,7 @@ pub mod pallet {
|
||||
/// its metadata, attributes, who can mint it (`None` for anyone) and until what block
|
||||
/// number.
|
||||
/// - `signature`: The signature of the `data` object.
|
||||
/// - `signer`: The `data` object's signer. Should be an owner of the collection.
|
||||
/// - `signer`: The `data` object's signer. Should be an Issuer of the collection.
|
||||
///
|
||||
/// Emits `Issued` on success.
|
||||
/// Emits `AttributeSet` if the attributes were provided.
|
||||
@@ -1853,7 +1851,7 @@ pub mod pallet {
|
||||
/// - `data`: The pre-signed approval that consists of the information about the item,
|
||||
/// attributes to update and until what block number.
|
||||
/// - `signature`: The signature of the `data` object.
|
||||
/// - `signer`: The `data` object's signer. Should be an owner of the collection for the
|
||||
/// - `signer`: The `data` object's signer. Should be an Admin of the collection for the
|
||||
/// `CollectionOwner` namespace.
|
||||
///
|
||||
/// Emits `AttributeSet` for each provided attribute.
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 super::*;
|
||||
use frame_support::{log, traits::OnRuntimeUpgrade};
|
||||
|
||||
pub mod v1 {
|
||||
use frame_support::{pallet_prelude::*, weights::Weight};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Decode)]
|
||||
pub struct OldCollectionDetails<AccountId, DepositBalance> {
|
||||
pub owner: AccountId,
|
||||
pub owner_deposit: DepositBalance,
|
||||
pub items: u32,
|
||||
pub item_metadatas: u32,
|
||||
pub attributes: u32,
|
||||
}
|
||||
|
||||
impl<AccountId, DepositBalance> OldCollectionDetails<AccountId, DepositBalance> {
|
||||
fn migrate_to_v1(self, item_configs: u32) -> CollectionDetails<AccountId, DepositBalance> {
|
||||
CollectionDetails {
|
||||
owner: self.owner,
|
||||
owner_deposit: self.owner_deposit,
|
||||
items: self.items,
|
||||
item_metadatas: self.item_metadatas,
|
||||
item_configs,
|
||||
attributes: self.attributes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let current_version = Pallet::<T>::current_storage_version();
|
||||
let onchain_version = Pallet::<T>::on_chain_storage_version();
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Running migration with current storage version {:?} / onchain {:?}",
|
||||
current_version,
|
||||
onchain_version
|
||||
);
|
||||
|
||||
if onchain_version == 0 && current_version == 1 {
|
||||
let mut translated = 0u64;
|
||||
let mut configs_iterated = 0u64;
|
||||
Collection::<T>::translate::<
|
||||
OldCollectionDetails<T::AccountId, DepositBalanceOf<T>>,
|
||||
_,
|
||||
>(|key, old_value| {
|
||||
let item_configs = ItemConfigOf::<T>::iter_prefix(&key).count() as u32;
|
||||
configs_iterated += item_configs as u64;
|
||||
translated.saturating_inc();
|
||||
Some(old_value.migrate_to_v1(item_configs))
|
||||
});
|
||||
|
||||
current_version.put::<Pallet<T>>();
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Upgraded {} records, storage to version {:?}",
|
||||
translated,
|
||||
current_version
|
||||
);
|
||||
T::DbWeight::get().reads_writes(translated + configs_iterated + 1, translated + 1)
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Migration did not execute. This probably should be removed"
|
||||
);
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
|
||||
let current_version = Pallet::<T>::current_storage_version();
|
||||
let onchain_version = Pallet::<T>::on_chain_storage_version();
|
||||
ensure!(onchain_version == 0 && current_version == 1, "migration from version 0 to 1.");
|
||||
let prev_count = Collection::<T>::iter().count();
|
||||
Ok((prev_count as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(prev_count: Vec<u8>) -> Result<(), &'static str> {
|
||||
let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect(
|
||||
"the state parameter should be something that was generated by pre_upgrade",
|
||||
);
|
||||
let post_count = Collection::<T>::iter().count() as u32;
|
||||
assert_eq!(
|
||||
prev_count, post_count,
|
||||
"the records count before and after the migration should be the same"
|
||||
);
|
||||
|
||||
ensure!(Pallet::<T>::on_chain_storage_version() == 1, "wrong storage version");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,21 +215,44 @@ fn lifecycle_should_work() {
|
||||
assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().items, 3);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_metadatas, 0);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_configs, 3);
|
||||
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 8);
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2)));
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 1);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 8);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![42, 42]));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 10);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 11);
|
||||
assert!(ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![69, 69]));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 13);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 14);
|
||||
assert!(ItemMetadataOf::<Test>::contains_key(0, 69));
|
||||
assert!(ItemConfigOf::<Test>::contains_key(0, 69));
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_eq!(w.item_metadatas, 2);
|
||||
assert_eq!(w.item_configs, 3);
|
||||
assert_noop!(
|
||||
Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w),
|
||||
Error::<Test>::CollectionNotEmpty
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::set_attribute(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Some(69),
|
||||
AttributeNamespace::CollectionOwner,
|
||||
bvec![0],
|
||||
bvec![0],
|
||||
));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70));
|
||||
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_eq!(w.items, 3);
|
||||
assert_eq!(w.item_metadatas, 2);
|
||||
assert_eq!(w.attributes, 1);
|
||||
assert_eq!(w.item_metadatas, 0);
|
||||
assert_eq!(w.item_configs, 0);
|
||||
assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 0);
|
||||
|
||||
@@ -240,6 +263,8 @@ fn lifecycle_should_work() {
|
||||
assert!(!CollectionMetadataOf::<Test>::contains_key(0));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 69));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 69));
|
||||
assert_eq!(attributes(0), vec![]);
|
||||
assert_eq!(collections(), vec![]);
|
||||
assert_eq!(items(), vec![]);
|
||||
});
|
||||
@@ -256,14 +281,51 @@ fn destroy_with_bad_witness_should_not_work() {
|
||||
));
|
||||
|
||||
let w = Collection::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
|
||||
assert_noop!(
|
||||
Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w),
|
||||
Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
DestroyWitness { item_configs: 1, ..w }
|
||||
),
|
||||
Error::<Test>::BadWitness
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroy_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::make_free_balance_be(&account(1), 100);
|
||||
assert_ok!(Nfts::create(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
account(1),
|
||||
collection_config_with_all_settings_enabled()
|
||||
));
|
||||
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None));
|
||||
assert_noop!(
|
||||
Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Nfts::get_destroy_witness(&0).unwrap()
|
||||
),
|
||||
Error::<Test>::CollectionNotEmpty
|
||||
);
|
||||
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42));
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_configs, 1);
|
||||
assert_eq!(ItemConfigOf::<Test>::iter_prefix(0).count() as u32, 1);
|
||||
assert!(ItemConfigOf::<Test>::contains_key(0, 42));
|
||||
assert_ok!(Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Nfts::get_destroy_witness(&0).unwrap()
|
||||
));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 42));
|
||||
assert_eq!(ItemConfigOf::<Test>::iter_prefix(0).count() as u32, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mint_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -491,8 +553,9 @@ fn origin_guards_should_work() {
|
||||
Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 69, account(2), None),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 43, account(2), None));
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42, None),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 43),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
@@ -536,13 +599,13 @@ fn transfer_owner_should_work() {
|
||||
|
||||
// Mint and set metadata now and make sure that deposit gets transferred back.
|
||||
assert_ok!(Nfts::set_collection_metadata(
|
||||
RuntimeOrigin::signed(account(2)),
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
bvec![0u8; 20],
|
||||
));
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 1);
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(2)), 0, 42, bvec![0u8; 20]));
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20]));
|
||||
assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(3)), Some(0)));
|
||||
assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(3)));
|
||||
assert_eq!(collections(), vec![(account(3), 0)]);
|
||||
@@ -552,8 +615,9 @@ fn transfer_owner_should_work() {
|
||||
assert_eq!(Balances::reserved_balance(&account(3)), 44);
|
||||
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 0);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 1);
|
||||
// reserved_balance of accounts 1 & 2 should be unchanged:
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 1);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
|
||||
// 2's acceptance from before is reset when it became an owner, so it cannot be transferred
|
||||
// without a fresh acceptance.
|
||||
@@ -583,8 +647,14 @@ fn set_team_should_work() {
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 42, account(2), None));
|
||||
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
|
||||
assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42, None));
|
||||
assert_noop!(
|
||||
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -594,7 +664,7 @@ fn set_collection_metadata_should_work() {
|
||||
// Cannot add metadata to unknown item
|
||||
assert_noop!(
|
||||
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 20]),
|
||||
Error::<Test>::NoConfig,
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
assert_ok!(Nfts::force_create(
|
||||
RuntimeOrigin::root(),
|
||||
@@ -668,7 +738,7 @@ fn set_collection_metadata_should_work() {
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 1),
|
||||
Error::<Test>::UnknownCollection
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0),
|
||||
@@ -743,7 +813,7 @@ fn set_item_metadata_should_work() {
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 1, 42),
|
||||
Error::<Test>::MetadataNotFound,
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
@@ -832,6 +902,7 @@ fn set_collection_owner_attributes_should_work() {
|
||||
);
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 16);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0));
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w));
|
||||
assert_eq!(attributes(0), vec![]);
|
||||
@@ -997,7 +1068,7 @@ fn set_item_owner_attributes_should_work() {
|
||||
assert_eq!(Balances::reserved_balance(account(3)), 13);
|
||||
|
||||
// validate attributes on item deletion
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0, None));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0));
|
||||
assert_eq!(
|
||||
attributes(0),
|
||||
vec![
|
||||
@@ -1317,7 +1388,7 @@ fn preserve_config_for_frozen_items() {
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None));
|
||||
|
||||
// if the item is not locked/frozen then the config gets deleted on item burn
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1, Some(account(1))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 1));
|
||||
|
||||
// lock the item and ensure the config stays unchanged
|
||||
@@ -1329,7 +1400,7 @@ fn preserve_config_for_frozen_items() {
|
||||
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
|
||||
assert_eq!(config, expect_config);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0, Some(account(1))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0));
|
||||
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
|
||||
assert_eq!(config, expect_config);
|
||||
|
||||
@@ -1405,6 +1476,7 @@ fn force_update_collection_should_work() {
|
||||
|
||||
Balances::make_free_balance_be(&account(5), 100);
|
||||
assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, account(5)));
|
||||
assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, account(2), account(5), account(4)));
|
||||
assert_eq!(collections(), vec![(account(5), 0)]);
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 2);
|
||||
assert_eq!(Balances::reserved_balance(account(5)), 63);
|
||||
@@ -1475,7 +1547,7 @@ fn burn_works() {
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(5))),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
|
||||
@@ -1496,16 +1568,12 @@ fn burn_works() {
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 2);
|
||||
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42, None),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(6))),
|
||||
Error::<Test>::WrongOwner
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(5))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 69, Some(account(5))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69));
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 0);
|
||||
});
|
||||
}
|
||||
@@ -1819,21 +1887,21 @@ fn cancel_approval_works_with_admin() {
|
||||
None
|
||||
));
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 1, 42, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 43, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(4)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)),
|
||||
Error::<Test>::NotDelegate
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(3)));
|
||||
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)));
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)),
|
||||
Error::<Test>::NotDelegate
|
||||
);
|
||||
});
|
||||
@@ -3013,6 +3081,7 @@ fn add_remove_item_attributes_approval_should_work() {
|
||||
#[test]
|
||||
fn pre_signed_mints_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_0 = account(0);
|
||||
let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
|
||||
let user_1_signer = MultiSigner::Sr25519(user_1_pair.public());
|
||||
let user_1 = user_1_signer.clone().into_account();
|
||||
@@ -3029,10 +3098,10 @@ fn pre_signed_mints_should_work() {
|
||||
let user_2 = account(2);
|
||||
let user_3 = account(3);
|
||||
|
||||
Balances::make_free_balance_be(&user_1, 100);
|
||||
Balances::make_free_balance_be(&user_0, 100);
|
||||
Balances::make_free_balance_be(&user_2, 100);
|
||||
assert_ok!(Nfts::create(
|
||||
RuntimeOrigin::signed(user_1.clone()),
|
||||
RuntimeOrigin::signed(user_0.clone()),
|
||||
user_1.clone(),
|
||||
collection_config_with_all_settings_enabled(),
|
||||
));
|
||||
@@ -3069,7 +3138,7 @@ fn pre_signed_mints_should_work() {
|
||||
assert_eq!(deposit.account, Some(user_2.clone()));
|
||||
assert_eq!(deposit.amount, 3);
|
||||
|
||||
assert_eq!(Balances::free_balance(&user_1), 100 - 2); // 2 - collection deposit
|
||||
assert_eq!(Balances::free_balance(&user_0), 100 - 2); // 2 - collection deposit
|
||||
assert_eq!(Balances::free_balance(&user_2), 100 - 1 - 3 - 6); // 1 - item deposit, 3 - metadata, 6 - attributes
|
||||
|
||||
assert_noop!(
|
||||
@@ -3082,7 +3151,7 @@ fn pre_signed_mints_should_work() {
|
||||
Error::<Test>::AlreadyExists
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0, Some(user_2.clone())));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0));
|
||||
assert_eq!(Balances::free_balance(&user_2), 100 - 6);
|
||||
|
||||
// validate the `only_account` field
|
||||
|
||||
@@ -92,6 +92,8 @@ pub struct CollectionDetails<AccountId, DepositBalance> {
|
||||
pub(super) items: u32,
|
||||
/// The total number of outstanding item metadata of this collection.
|
||||
pub(super) item_metadatas: u32,
|
||||
/// The total number of outstanding item configs of this collection.
|
||||
pub(super) item_configs: u32,
|
||||
/// The total number of attributes for this collection.
|
||||
pub(super) attributes: u32,
|
||||
}
|
||||
@@ -99,12 +101,12 @@ pub struct CollectionDetails<AccountId, DepositBalance> {
|
||||
/// Witness data for the destroy transactions.
|
||||
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct DestroyWitness {
|
||||
/// The total number of outstanding items of this collection.
|
||||
#[codec(compact)]
|
||||
pub items: u32,
|
||||
/// The total number of items in this collection that have outstanding item metadata.
|
||||
#[codec(compact)]
|
||||
pub item_metadatas: u32,
|
||||
/// The total number of outstanding item configs of this collection.
|
||||
#[codec(compact)]
|
||||
pub item_configs: u32,
|
||||
/// The total number of attributes for this collection.
|
||||
#[codec(compact)]
|
||||
pub attributes: u32,
|
||||
@@ -113,8 +115,8 @@ pub struct DestroyWitness {
|
||||
impl<AccountId, DepositBalance> CollectionDetails<AccountId, DepositBalance> {
|
||||
pub fn destroy_witness(&self) -> DestroyWitness {
|
||||
DestroyWitness {
|
||||
items: self.items,
|
||||
item_metadatas: self.item_metadatas,
|
||||
item_configs: self.item_configs,
|
||||
attributes: self.attributes,
|
||||
}
|
||||
}
|
||||
|
||||
+464
-560
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user