Files
pezkuwi-subxt/substrate/frame/nfts/src/impl_nonfungibles.rs
T
Dastan 0bcebac4fc Expose collection attributes from Inspect trait (#1914)
# Description

- What does this PR do?

While working with `pallet_nfts` through `nonfungibles_v2` traits
`Inspect, Mutate`, I found out that once you have set the collection
attribute with `<Nfts as Mutate>::set_collection_attribute()`, it's not
possible to read it with `<Nfts as Inspect>::collection_attribute()`
since they use different `namespace` values. When setting the attribute,
`AttributeNamespace::Pallet` is used, while
`AttributeNamespace::CollectionOwner` is used when reading.

more context:
https://github.com/freeverseio/laos/issues/7#issuecomment-1766137370

This PR makes `item` an optional parameter in
`Inspect::system_attribute()`, to be able to read collection attributes.

- Why are these changes needed?

To be able to read collection level attributes when reading attributes
of the collection. It will be possible to read collection attributes by
passing `None` for `item`

- How were these changes implemented and what do they affect?

`NftsApi` is also affected and `NftsApi::system_attribute()` now accepts
optional `item` parameter.

## Breaking change

Because of the change in the `NftsApi::system_attribute()` method's
`item` param, parachains who integrated the `NftsApi` need to update
their API code and frontend integrations accordingly. AssetHubs are
unaffected since the NftsApi wasn't released on those parachains yet.
2023-10-26 09:52:12 +00:00

508 lines
15 KiB
Rust

// 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.
//! Implementations for `nonfungibles` traits.
use super::*;
use frame_support::{
ensure,
storage::KeyPrefixIterator,
traits::{tokens::nonfungibles_v2::*, Get},
BoundedSlice,
};
use sp_runtime::{DispatchError, DispatchResult};
use sp_std::prelude::*;
impl<T: Config<I>, I: 'static> Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
type ItemId = T::ItemId;
type CollectionId = T::CollectionId;
fn owner(
collection: &Self::CollectionId,
item: &Self::ItemId,
) -> Option<<T as SystemConfig>::AccountId> {
Item::<T, I>::get(collection, item).map(|a| a.owner)
}
fn collection_owner(collection: &Self::CollectionId) -> Option<<T as SystemConfig>::AccountId> {
Collection::<T, I>::get(collection).map(|a| a.owner)
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
ItemMetadataOf::<T, I>::get(collection, item).map(|m| m.data.into())
} else {
let namespace = AttributeNamespace::CollectionOwner;
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, Some(item), namespace, key)).map(|a| a.0.into())
}
}
/// Returns the custom attribute value of `item` of `collection` corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn custom_attribute(
account: &T::AccountId,
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> Option<Vec<u8>> {
let namespace = Account::<T, I>::get((account, collection, item))
.map(|_| AttributeNamespace::ItemOwner)
.unwrap_or_else(|| AttributeNamespace::Account(account.clone()));
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, Some(item), namespace, key)).map(|a| a.0.into())
}
/// Returns the system attribute value of `item` of `collection` corresponding to `key` if
/// `item` is `Some`. Otherwise, returns the system attribute value of `collection`
/// corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn system_attribute(
collection: &Self::CollectionId,
item: Option<&Self::ItemId>,
key: &[u8],
) -> Option<Vec<u8>> {
let namespace = AttributeNamespace::Pallet;
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, item, namespace, key)).map(|a| a.0.into())
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
CollectionMetadataOf::<T, I>::get(collection).map(|m| m.data.into())
} else {
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((
collection,
Option::<T::ItemId>::None,
AttributeNamespace::CollectionOwner,
key,
))
.map(|a| a.0.into())
}
}
/// Returns `true` if the `item` of `collection` may be transferred.
///
/// Default implementation is that all items are transferable.
fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool {
use PalletAttributes::TransferDisabled;
match Self::has_system_attribute(&collection, &item, TransferDisabled) {
Ok(transfer_disabled) if transfer_disabled => return false,
_ => (),
}
match (
CollectionConfigOf::<T, I>::get(collection),
ItemConfigOf::<T, I>::get(collection, item),
) {
(Some(cc), Some(ic))
if cc.is_setting_enabled(CollectionSetting::TransferableItems) &&
ic.is_setting_enabled(ItemSetting::Transferable) =>
true,
_ => false,
}
}
}
impl<T: Config<I>, I: 'static> InspectRole<<T as SystemConfig>::AccountId> for Pallet<T, I> {
fn is_issuer(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Issuer)
}
fn is_admin(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Admin)
}
fn is_freezer(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Freezer)
}
}
impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId, CollectionConfigFor<T, I>>
for Pallet<T, I>
{
/// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`.
fn create_collection(
who: &T::AccountId,
admin: &T::AccountId,
config: &CollectionConfigFor<T, I>,
) -> Result<T::CollectionId, DispatchError> {
// DepositRequired can be disabled by calling the force_create() only
ensure!(
!config.has_disabled_setting(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
let collection = NextCollectionId::<T, I>::get()
.or(T::CollectionId::initial_value())
.ok_or(Error::<T, I>::UnknownCollection)?;
Self::do_create_collection(
collection,
who.clone(),
admin.clone(),
*config,
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> {
type DestroyWitness = DestroyWitness;
fn get_destroy_witness(collection: &Self::CollectionId) -> Option<DestroyWitness> {
Collection::<T, I>::get(collection).map(|a| a.destroy_witness())
}
fn destroy(
collection: Self::CollectionId,
witness: Self::DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> Result<Self::DestroyWitness, DispatchError> {
Self::do_destroy_collection(collection, witness, maybe_check_owner)
}
}
impl<T: Config<I>, I: 'static> Mutate<<T as SystemConfig>::AccountId, ItemConfig> for Pallet<T, I> {
fn mint_into(
collection: &Self::CollectionId,
item: &Self::ItemId,
who: &T::AccountId,
item_config: &ItemConfig,
deposit_collection_owner: bool,
) -> DispatchResult {
Self::do_mint(
*collection,
*item,
match deposit_collection_owner {
true => None,
false => Some(who.clone()),
},
who.clone(),
*item_config,
|_, _| Ok(()),
)
}
fn burn(
collection: &Self::CollectionId,
item: &Self::ItemId,
maybe_check_owner: Option<&T::AccountId>,
) -> DispatchResult {
Self::do_burn(*collection, *item, |d| {
if let Some(check_owner) = maybe_check_owner {
if &d.owner != check_owner {
return Err(Error::<T, I>::NoPermission.into())
}
}
Ok(())
})
}
fn set_attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
Some(*item),
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_attribute(collection, item, k, v)
})
})
}
fn set_collection_attribute(
collection: &Self::CollectionId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
None,
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_collection_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_collection_attribute(
collection, k, v,
)
})
})
}
fn set_item_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
item: &Self::ItemId,
data: &[u8],
) -> DispatchResult {
Self::do_set_item_metadata(
who.cloned(),
*collection,
*item,
Self::construct_metadata(data.to_vec())?,
None,
)
}
fn set_collection_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
data: &[u8],
) -> DispatchResult {
Self::do_set_collection_metadata(
who.cloned(),
*collection,
Self::construct_metadata(data.to_vec())?,
)
}
fn clear_attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> DispatchResult {
Self::do_clear_attribute(
None,
*collection,
Some(*item),
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
)
}
fn clear_typed_attribute<K: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
) -> DispatchResult {
key.using_encoded(|k| {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_attribute(collection, item, k)
})
}
fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult {
Self::do_clear_attribute(
None,
*collection,
None,
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
)
}
fn clear_typed_collection_attribute<K: Encode>(
collection: &Self::CollectionId,
key: &K,
) -> DispatchResult {
key.using_encoded(|k| {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_collection_attribute(collection, k)
})
}
fn clear_item_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
item: &Self::ItemId,
) -> DispatchResult {
Self::do_clear_item_metadata(who.cloned(), *collection, *item)
}
fn clear_collection_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
) -> DispatchResult {
Self::do_clear_collection_metadata(who.cloned(), *collection)
}
}
impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> {
fn transfer(
collection: &Self::CollectionId,
item: &Self::ItemId,
destination: &T::AccountId,
) -> DispatchResult {
Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(()))
}
fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult {
let transfer_disabled =
Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?;
// Can't lock the item twice
if transfer_disabled {
return Err(Error::<T, I>::ItemLocked.into())
}
<Self as Mutate<T::AccountId, ItemConfig>>::set_attribute(
collection,
item,
&PalletAttributes::<Self::CollectionId>::TransferDisabled.encode(),
&[],
)
}
fn enable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_attribute(
collection,
item,
&PalletAttributes::<Self::CollectionId>::TransferDisabled.encode(),
)
}
}
impl<T: Config<I>, I: 'static> Trading<T::AccountId, ItemPrice<T, I>> for Pallet<T, I> {
fn buy_item(
collection: &Self::CollectionId,
item: &Self::ItemId,
buyer: &T::AccountId,
bid_price: &ItemPrice<T, I>,
) -> DispatchResult {
Self::do_buy_item(*collection, *item, buyer.clone(), *bid_price)
}
fn set_price(
collection: &Self::CollectionId,
item: &Self::ItemId,
sender: &T::AccountId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<T::AccountId>,
) -> DispatchResult {
Self::do_set_price(*collection, *item, sender.clone(), price, whitelisted_buyer)
}
fn item_price(collection: &Self::CollectionId, item: &Self::ItemId) -> Option<ItemPrice<T, I>> {
ItemPriceOf::<T, I>::get(collection, item).map(|a| a.0)
}
}
impl<T: Config<I>, I: 'static> InspectEnumerable<T::AccountId> for Pallet<T, I> {
type CollectionsIterator = KeyPrefixIterator<<T as Config<I>>::CollectionId>;
type ItemsIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
type OwnedIterator =
KeyPrefixIterator<(<T as Config<I>>::CollectionId, <T as Config<I>>::ItemId)>;
type OwnedInCollectionIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
/// Returns an iterator of the collections in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn collections() -> Self::CollectionsIterator {
Collection::<T, I>::iter_keys()
}
/// Returns an iterator of the items of a `collection` in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn items(collection: &Self::CollectionId) -> Self::ItemsIterator {
Item::<T, I>::iter_key_prefix(collection)
}
/// Returns an iterator of the items of all collections owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned(who: &T::AccountId) -> Self::OwnedIterator {
Account::<T, I>::iter_key_prefix((who,))
}
/// Returns an iterator of the items of `collection` owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned_in_collection(
collection: &Self::CollectionId,
who: &T::AccountId,
) -> Self::OwnedInCollectionIterator {
Account::<T, I>::iter_key_prefix((who, collection))
}
}