1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
// 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(())
})
}
}