pallet_nfts/features/roles.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 configure account roles for existing collections.
19
20use crate::*;
21use alloc::{collections::btree_map::BTreeMap, vec::Vec};
22use frame_support::pallet_prelude::*;
23
24impl<T: Config<I>, I: 'static> Pallet<T, I> {
25 /// Set the team roles for a specific collection.
26 ///
27 /// - `maybe_check_owner`: An optional account ID used to check ownership permission. If `None`,
28 /// it is considered as the root.
29 /// - `collection`: The ID of the collection for which to set the team roles.
30 /// - `issuer`: An optional account ID representing the issuer role.
31 /// - `admin`: An optional account ID representing the admin role.
32 /// - `freezer`: An optional account ID representing the freezer role.
33 ///
34 /// This function allows the owner or the root (when `maybe_check_owner` is `None`) to set the
35 /// team roles for a specific collection. The root can change the role from `None` to
36 /// `Some(account)`, but other roles can only be updated by the root or an account with an
37 /// existing role in the collection.
38 pub(crate) fn do_set_team(
39 maybe_check_owner: Option<T::AccountId>,
40 collection: T::CollectionId,
41 issuer: Option<T::AccountId>,
42 admin: Option<T::AccountId>,
43 freezer: Option<T::AccountId>,
44 ) -> DispatchResult {
45 Collection::<T, I>::try_mutate(collection, |maybe_details| {
46 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
47 let is_root = maybe_check_owner.is_none();
48 if let Some(check_origin) = maybe_check_owner {
49 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
50 }
51
52 let roles_map = [
53 (issuer.clone(), CollectionRole::Issuer),
54 (admin.clone(), CollectionRole::Admin),
55 (freezer.clone(), CollectionRole::Freezer),
56 ];
57
58 // only root can change the role from `None` to `Some(account)`
59 if !is_root {
60 for (account, role) in roles_map.iter() {
61 if account.is_some() {
62 ensure!(
63 Self::find_account_by_role(&collection, *role).is_some(),
64 Error::<T, I>::NoPermission
65 );
66 }
67 }
68 }
69
70 let roles = roles_map
71 .into_iter()
72 .filter_map(|(account, role)| account.map(|account| (account, role)))
73 .collect();
74
75 let account_to_role = Self::group_roles_by_account(roles);
76
77 // Delete the previous records.
78 Self::clear_roles(&collection)?;
79
80 // Insert new records.
81 for (account, roles) in account_to_role {
82 CollectionRoleOf::<T, I>::insert(&collection, &account, roles);
83 }
84
85 Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
86 Ok(())
87 })
88 }
89
90 /// Clears all the roles in a specified collection.
91 ///
92 /// - `collection_id`: A collection to clear the roles in.
93 ///
94 /// This function clears all the roles associated with the given `collection_id`. It throws an
95 /// error if some of the roles were left in storage, indicating that the maximum number of roles
96 /// may need to be adjusted.
97 pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> {
98 let res = CollectionRoleOf::<T, I>::clear_prefix(
99 &collection_id,
100 CollectionRoles::max_roles() as u32,
101 None,
102 );
103 ensure!(res.maybe_cursor.is_none(), Error::<T, I>::RolesNotCleared);
104 Ok(())
105 }
106
107 /// Returns true if a specified account has a provided role within that collection.
108 ///
109 /// - `collection_id`: A collection to check the role in.
110 /// - `account_id`: An account to check the role for.
111 /// - `role`: A role to validate.
112 ///
113 /// Returns `true` if the account has the specified role, `false` otherwise.
114 pub(crate) fn has_role(
115 collection_id: &T::CollectionId,
116 account_id: &T::AccountId,
117 role: CollectionRole,
118 ) -> bool {
119 CollectionRoleOf::<T, I>::get(&collection_id, &account_id)
120 .map_or(false, |roles| roles.has_role(role))
121 }
122
123 /// Finds the account by a provided role within a collection.
124 ///
125 /// - `collection_id`: A collection to check the role in.
126 /// - `role`: A role to find the account for.
127 ///
128 /// Returns `Some(T::AccountId)` if the record was found, `None` otherwise.
129 pub(crate) fn find_account_by_role(
130 collection_id: &T::CollectionId,
131 role: CollectionRole,
132 ) -> Option<T::AccountId> {
133 CollectionRoleOf::<T, I>::iter_prefix(&collection_id).into_iter().find_map(
134 |(account, roles)| if roles.has_role(role) { Some(account.clone()) } else { None },
135 )
136 }
137
138 /// Groups provided roles by account, given one account could have multiple roles.
139 ///
140 /// - `input`: A vector of (Account, Role) tuples.
141 ///
142 /// Returns a grouped vector of `(Account, Roles)` tuples.
143 pub fn group_roles_by_account(
144 input: Vec<(T::AccountId, CollectionRole)>,
145 ) -> Vec<(T::AccountId, CollectionRoles)> {
146 let mut result = BTreeMap::new();
147 for (account, role) in input.into_iter() {
148 result.entry(account).or_insert(CollectionRoles::none()).add_role(role);
149 }
150 result.into_iter().collect()
151 }
152}