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}