Files
pezkuwi-subxt/substrate/frame/nfts/src/features/transfer.rs
T
Bastian Köcher 84d6342cd2 pallet-uniques/nfts: Small optimizations (#2754)
Use `contains_key` to not require decoding the actual value.
2023-12-19 17:17:14 +01:00

235 lines
8.6 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.
//! This module contains helper methods to perform the transfer functionalities
//! of the NFTs pallet.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Transfer an NFT to the specified destination account.
///
/// - `collection`: The ID of the collection to which the NFT belongs.
/// - `item`: The ID of the NFT to transfer.
/// - `dest`: The destination account to which the NFT will be transferred.
/// - `with_details`: A closure that provides access to the collection and item details,
/// allowing customization of the transfer process.
///
/// This function performs the actual transfer of an NFT to the destination account.
/// It checks various conditions like item lock status and transferability settings
/// for the collection and item before transferring the NFT.
///
/// # Errors
///
/// This function returns a dispatch error in the following cases:
/// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)).
/// - If the item ID is invalid ([`UnknownItem`](crate::Error::UnknownItem)).
/// - If the item is locked or transferring it is disabled
/// ([`ItemLocked`](crate::Error::ItemLocked)).
/// - If the collection or item is non-transferable
/// ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)).
pub fn do_transfer(
collection: T::CollectionId,
item: T::ItemId,
dest: T::AccountId,
with_details: impl FnOnce(
&CollectionDetailsFor<T, I>,
&mut ItemDetailsFor<T, I>,
) -> DispatchResult,
) -> DispatchResult {
// Retrieve collection details.
let collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
// Ensure the item is not locked.
ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
// Ensure the item is not transfer disabled on the system level attribute.
ensure!(
!Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?,
Error::<T, I>::ItemLocked
);
// Retrieve collection config and check if items are transferable.
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
// Retrieve item config and check if the item is transferable.
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
item_config.is_setting_enabled(ItemSetting::Transferable),
Error::<T, I>::ItemLocked
);
// Retrieve the item details.
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
// Perform the transfer with custom details using the provided closure.
with_details(&collection_details, &mut details)?;
// Update account ownership information.
Account::<T, I>::remove((&details.owner, &collection, &item));
Account::<T, I>::insert((&dest, &collection, &item), ());
let origin = details.owner;
details.owner = dest;
// The approved accounts have to be reset to `None`, because otherwise pre-approve attack
// would be possible, where the owner can approve their second account before making the
// transaction and then claiming the item back.
details.approvals.clear();
// Update item details.
Item::<T, I>::insert(&collection, &item, &details);
ItemPriceOf::<T, I>::remove(&collection, &item);
PendingSwapOf::<T, I>::remove(&collection, &item);
// Emit `Transferred` event.
Self::deposit_event(Event::Transferred {
collection,
item,
from: origin,
to: details.owner,
});
Ok(())
}
/// Transfer ownership of a collection to another account.
///
/// - `origin`: The account requesting the transfer.
/// - `collection`: The ID of the collection to transfer ownership.
/// - `owner`: The new account that will become the owner of the collection.
///
/// This function transfers the ownership of a collection to the specified account.
/// It performs checks to ensure that the `origin` is the current owner and that the
/// new owner is an acceptable account based on the collection's acceptance settings.
pub(crate) fn do_transfer_ownership(
origin: T::AccountId,
collection: T::CollectionId,
new_owner: T::AccountId,
) -> DispatchResult {
// Check if the new owner is acceptable based on the collection's acceptance settings.
let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
// Try to retrieve and mutate the collection details.
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
// Check if the `origin` is the current owner of the collection.
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
if details.owner == new_owner {
return Ok(())
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&new_owner,
details.owner_deposit,
Reserved,
)?;
// Update account ownership information.
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
details.owner = new_owner.clone();
OwnershipAcceptance::<T, I>::remove(&new_owner);
frame_system::Pallet::<T>::dec_consumers(&new_owner);
// Emit `OwnerChanged` event.
Self::deposit_event(Event::OwnerChanged { collection, new_owner });
Ok(())
})
}
/// Set or unset the ownership acceptance for an account regarding a specific collection.
///
/// - `who`: The account for which to set or unset the ownership acceptance.
/// - `maybe_collection`: An optional collection ID to set the ownership acceptance.
///
/// If `maybe_collection` is `Some(collection)`, then the account `who` will accept
/// ownership transfers for the specified collection. If `maybe_collection` is `None`,
/// then the account `who` will unset the ownership acceptance, effectively refusing
/// ownership transfers for any collection.
pub(crate) fn do_set_accept_ownership(
who: T::AccountId,
maybe_collection: Option<T::CollectionId>,
) -> DispatchResult {
let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
match (exists, maybe_collection.is_some()) {
(false, true) => {
frame_system::Pallet::<T>::inc_consumers(&who)?;
},
(true, false) => {
frame_system::Pallet::<T>::dec_consumers(&who);
},
_ => {},
}
if let Some(collection) = maybe_collection.as_ref() {
OwnershipAcceptance::<T, I>::insert(&who, collection);
} else {
OwnershipAcceptance::<T, I>::remove(&who);
}
// Emit `OwnershipAcceptanceChanged` event.
Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
Ok(())
}
/// Forcefully change the owner of a collection.
///
/// - `collection`: The ID of the collection to change ownership.
/// - `owner`: The new account that will become the owner of the collection.
///
/// This function allows for changing the ownership of a collection without any checks.
/// It moves the deposit to the new owner, updates the collection's owner, and emits
/// an `OwnerChanged` event.
pub(crate) fn do_force_collection_owner(
collection: T::CollectionId,
owner: T::AccountId,
) -> DispatchResult {
// Try to retrieve and mutate the collection details.
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
if details.owner == owner {
return Ok(())
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&owner,
details.owner_deposit,
Reserved,
)?;
// Update collection accounts and set the new owner.
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
details.owner = owner.clone();
// Emit `OwnerChanged` event.
Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
Ok(())
})
}
}