mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-22 10:15:42 +00:00
0bcebac4fc
# 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.
508 lines
15 KiB
Rust
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))
|
|
}
|
|
}
|