referrerpolicy=no-referrer-when-downgrade

pallet_nfts/features/
transfer.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! This module contains helper methods to perform the transfer functionalities
19//! of the NFTs pallet.
20
21use crate::*;
22use frame_support::pallet_prelude::*;
23
24impl<T: Config<I>, I: 'static> Pallet<T, I> {
25	/// Transfer an NFT to the specified destination account.
26	///
27	/// - `collection`: The ID of the collection to which the NFT belongs.
28	/// - `item`: The ID of the NFT to transfer.
29	/// - `dest`: The destination account to which the NFT will be transferred.
30	/// - `with_details`: A closure that provides access to the collection and item details,
31	///   allowing customization of the transfer process.
32	///
33	/// This function performs the actual transfer of an NFT to the destination account.
34	/// It checks various conditions like item lock status and transferability settings
35	/// for the collection and item before transferring the NFT.
36	///
37	/// # Errors
38	///
39	/// This function returns a dispatch error in the following cases:
40	/// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)).
41	/// - If the item ID is invalid ([`UnknownItem`](crate::Error::UnknownItem)).
42	/// - If the item is locked or transferring it is disabled
43	///   ([`ItemLocked`](crate::Error::ItemLocked)).
44	/// - If the collection or item is non-transferable
45	///   ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)).
46	pub fn do_transfer(
47		collection: T::CollectionId,
48		item: T::ItemId,
49		dest: T::AccountId,
50		with_details: impl FnOnce(
51			&CollectionDetailsFor<T, I>,
52			&mut ItemDetailsFor<T, I>,
53		) -> DispatchResult,
54	) -> DispatchResult {
55		// Retrieve collection details.
56		let collection_details =
57			Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
58
59		// Ensure the item is not locked.
60		ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
61
62		// Ensure the item is not transfer disabled on the system level attribute.
63		ensure!(
64			!Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?,
65			Error::<T, I>::ItemLocked
66		);
67
68		// Retrieve collection config and check if items are transferable.
69		let collection_config = Self::get_collection_config(&collection)?;
70		ensure!(
71			collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
72			Error::<T, I>::ItemsNonTransferable
73		);
74
75		// Retrieve item config and check if the item is transferable.
76		let item_config = Self::get_item_config(&collection, &item)?;
77		ensure!(
78			item_config.is_setting_enabled(ItemSetting::Transferable),
79			Error::<T, I>::ItemLocked
80		);
81
82		// Retrieve the item details.
83		let mut details =
84			Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
85
86		// Perform the transfer with custom details using the provided closure.
87		with_details(&collection_details, &mut details)?;
88
89		// Update account ownership information.
90		Account::<T, I>::remove((&details.owner, &collection, &item));
91		Account::<T, I>::insert((&dest, &collection, &item), ());
92		let origin = details.owner;
93		details.owner = dest;
94
95		// The approved accounts have to be reset to `None`, because otherwise pre-approve attack
96		// would be possible, where the owner can approve their second account before making the
97		// transaction and then claiming the item back.
98		details.approvals.clear();
99
100		// Update item details.
101		Item::<T, I>::insert(&collection, &item, &details);
102		ItemPriceOf::<T, I>::remove(&collection, &item);
103		PendingSwapOf::<T, I>::remove(&collection, &item);
104
105		// Emit `Transferred` event.
106		Self::deposit_event(Event::Transferred {
107			collection,
108			item,
109			from: origin,
110			to: details.owner,
111		});
112		Ok(())
113	}
114
115	/// Transfer ownership of a collection to another account.
116	///
117	/// - `origin`: The account requesting the transfer.
118	/// - `collection`: The ID of the collection to transfer ownership.
119	/// - `owner`: The new account that will become the owner of the collection.
120	///
121	/// This function transfers the ownership of a collection to the specified account.
122	/// It performs checks to ensure that the `origin` is the current owner and that the
123	/// new owner is an acceptable account based on the collection's acceptance settings.
124	pub(crate) fn do_transfer_ownership(
125		origin: T::AccountId,
126		collection: T::CollectionId,
127		new_owner: T::AccountId,
128	) -> DispatchResult {
129		// Check if the new owner is acceptable based on the collection's acceptance settings.
130		let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
131		ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
132
133		// Try to retrieve and mutate the collection details.
134		Collection::<T, I>::try_mutate(collection, |maybe_details| {
135			let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
136			// Check if the `origin` is the current owner of the collection.
137			ensure!(origin == details.owner, Error::<T, I>::NoPermission);
138			if details.owner == new_owner {
139				return Ok(())
140			}
141
142			// Move the deposit to the new owner.
143			T::Currency::repatriate_reserved(
144				&details.owner,
145				&new_owner,
146				details.owner_deposit,
147				Reserved,
148			)?;
149
150			// Update account ownership information.
151			CollectionAccount::<T, I>::remove(&details.owner, &collection);
152			CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
153
154			details.owner = new_owner.clone();
155			OwnershipAcceptance::<T, I>::remove(&new_owner);
156			frame_system::Pallet::<T>::dec_consumers(&new_owner);
157
158			// Emit `OwnerChanged` event.
159			Self::deposit_event(Event::OwnerChanged { collection, new_owner });
160			Ok(())
161		})
162	}
163	/// Set or unset the ownership acceptance for an account regarding a specific collection.
164	///
165	/// - `who`: The account for which to set or unset the ownership acceptance.
166	/// - `maybe_collection`: An optional collection ID to set the ownership acceptance.
167	///
168	/// If `maybe_collection` is `Some(collection)`, then the account `who` will accept
169	/// ownership transfers for the specified collection. If `maybe_collection` is `None`,
170	/// then the account `who` will unset the ownership acceptance, effectively refusing
171	/// ownership transfers for any collection.
172	pub(crate) fn do_set_accept_ownership(
173		who: T::AccountId,
174		maybe_collection: Option<T::CollectionId>,
175	) -> DispatchResult {
176		let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
177		match (exists, maybe_collection.is_some()) {
178			(false, true) => {
179				frame_system::Pallet::<T>::inc_consumers(&who)?;
180			},
181			(true, false) => {
182				frame_system::Pallet::<T>::dec_consumers(&who);
183			},
184			_ => {},
185		}
186		if let Some(collection) = maybe_collection.as_ref() {
187			OwnershipAcceptance::<T, I>::insert(&who, collection);
188		} else {
189			OwnershipAcceptance::<T, I>::remove(&who);
190		}
191
192		// Emit `OwnershipAcceptanceChanged` event.
193		Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
194		Ok(())
195	}
196
197	/// Forcefully change the owner of a collection.
198	///
199	/// - `collection`: The ID of the collection to change ownership.
200	/// - `owner`: The new account that will become the owner of the collection.
201	///
202	/// This function allows for changing the ownership of a collection without any checks.
203	/// It moves the deposit to the new owner, updates the collection's owner, and emits
204	/// an `OwnerChanged` event.
205	pub(crate) fn do_force_collection_owner(
206		collection: T::CollectionId,
207		owner: T::AccountId,
208	) -> DispatchResult {
209		// Try to retrieve and mutate the collection details.
210		Collection::<T, I>::try_mutate(collection, |maybe_details| {
211			let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
212			if details.owner == owner {
213				return Ok(())
214			}
215
216			// Move the deposit to the new owner.
217			T::Currency::repatriate_reserved(
218				&details.owner,
219				&owner,
220				details.owner_deposit,
221				Reserved,
222			)?;
223
224			// Update collection accounts and set the new owner.
225			CollectionAccount::<T, I>::remove(&details.owner, &collection);
226			CollectionAccount::<T, I>::insert(&owner, &collection, ());
227			details.owner = owner.clone();
228
229			// Emit `OwnerChanged` event.
230			Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
231			Ok(())
232		})
233	}
234}