pallet_nfts/features/approvals.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 functions for the approval logic implemented in the NFTs pallet.
19//! The bitflag [`PalletFeature::Approvals`] needs to be set in [`Config::Features`] for NFTs
20//! to have the functionality defined in this module.
21
22use crate::*;
23use frame_support::pallet_prelude::*;
24
25impl<T: Config<I>, I: 'static> Pallet<T, I> {
26 /// Approves the transfer of an item to a delegate.
27 ///
28 /// This function is used to approve the transfer of the specified `item` in the `collection` to
29 /// a `delegate`. If `maybe_check_origin` is specified, the function ensures that the
30 /// `check_origin` account is the owner of the item, granting them permission to approve the
31 /// transfer. The `delegate` is the account that will be allowed to take control of the item.
32 /// Optionally, a `deadline` can be specified to set a time limit for the approval. The
33 /// `deadline` is expressed in block numbers and is added to the current block number to
34 /// determine the absolute deadline for the approval. After approving the transfer, the function
35 /// emits the `TransferApproved` event.
36 ///
37 /// - `maybe_check_origin`: The optional account that is required to be the owner of the item,
38 /// granting permission to approve the transfer. If `None`, no permission check is performed.
39 /// - `collection`: The identifier of the collection containing the item to be transferred.
40 /// - `item`: The identifier of the item to be transferred.
41 /// - `delegate`: The account that will be allowed to take control of the item.
42 /// - `maybe_deadline`: The optional deadline (in block numbers) specifying the time limit for
43 /// the approval.
44 pub(crate) fn do_approve_transfer(
45 maybe_check_origin: Option<T::AccountId>,
46 collection: T::CollectionId,
47 item: T::ItemId,
48 delegate: T::AccountId,
49 maybe_deadline: Option<BlockNumberFor<T, I>>,
50 ) -> DispatchResult {
51 ensure!(
52 Self::is_pallet_feature_enabled(PalletFeature::Approvals),
53 Error::<T, I>::MethodDisabled
54 );
55 let mut details =
56 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
57
58 let collection_config = Self::get_collection_config(&collection)?;
59 ensure!(
60 collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
61 Error::<T, I>::ItemsNonTransferable
62 );
63
64 if let Some(check_origin) = maybe_check_origin {
65 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
66 }
67
68 let now = T::BlockNumberProvider::current_block_number();
69 let deadline = maybe_deadline.map(|d| d.saturating_add(now));
70
71 details
72 .approvals
73 .try_insert(delegate.clone(), deadline)
74 .map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
75 Item::<T, I>::insert(&collection, &item, &details);
76
77 Self::deposit_event(Event::TransferApproved {
78 collection,
79 item,
80 owner: details.owner,
81 delegate,
82 deadline,
83 });
84
85 Ok(())
86 }
87
88 /// Cancels the approval for the transfer of an item to a delegate.
89 ///
90 /// This function is used to cancel the approval for the transfer of the specified `item` in the
91 /// `collection` to a `delegate`. If `maybe_check_origin` is specified, the function ensures
92 /// that the `check_origin` account is the owner of the item or that the approval is past its
93 /// deadline, granting permission to cancel the approval. After canceling the approval, the
94 /// function emits the `ApprovalCancelled` event.
95 ///
96 /// - `maybe_check_origin`: The optional account that is required to be the owner of the item or
97 /// that the approval is past its deadline, granting permission to cancel the approval. If
98 /// `None`, no permission check is performed.
99 /// - `collection`: The identifier of the collection containing the item.
100 /// - `item`: The identifier of the item.
101 /// - `delegate`: The account that was previously allowed to take control of the item.
102 pub(crate) fn do_cancel_approval(
103 maybe_check_origin: Option<T::AccountId>,
104 collection: T::CollectionId,
105 item: T::ItemId,
106 delegate: T::AccountId,
107 ) -> DispatchResult {
108 let mut details =
109 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
110
111 let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::<T, I>::NotDelegate)?;
112
113 let is_past_deadline = if let Some(deadline) = maybe_deadline {
114 let now = T::BlockNumberProvider::current_block_number();
115 now > *deadline
116 } else {
117 false
118 };
119
120 if !is_past_deadline {
121 if let Some(check_origin) = maybe_check_origin {
122 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
123 }
124 }
125
126 details.approvals.remove(&delegate);
127 Item::<T, I>::insert(&collection, &item, &details);
128
129 Self::deposit_event(Event::ApprovalCancelled {
130 collection,
131 item,
132 owner: details.owner,
133 delegate,
134 });
135
136 Ok(())
137 }
138
139 /// Clears all transfer approvals for an item.
140 ///
141 /// This function is used to clear all transfer approvals for the specified `item` in the
142 /// `collection`. If `maybe_check_origin` is specified, the function ensures that the
143 /// `check_origin` account is the owner of the item, granting permission to clear all transfer
144 /// approvals. After clearing all approvals, the function emits the `AllApprovalsCancelled`
145 /// event.
146 ///
147 /// - `maybe_check_origin`: The optional account that is required to be the owner of the item,
148 /// granting permission to clear all transfer approvals. If `None`, no permission check is
149 /// performed.
150 /// - `collection`: The collection ID containing the item.
151 /// - `item`: The item ID for which transfer approvals will be cleared.
152 pub(crate) fn do_clear_all_transfer_approvals(
153 maybe_check_origin: Option<T::AccountId>,
154 collection: T::CollectionId,
155 item: T::ItemId,
156 ) -> DispatchResult {
157 let mut details =
158 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
159
160 if let Some(check_origin) = maybe_check_origin {
161 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
162 }
163
164 details.approvals.clear();
165 Item::<T, I>::insert(&collection, &item, &details);
166
167 Self::deposit_event(Event::AllApprovalsCancelled {
168 collection,
169 item,
170 owner: details.owner,
171 });
172
173 Ok(())
174 }
175}