1use crate::*;
24use frame_support::pallet_prelude::*;
25
26impl<T: Config<I>, I: 'static> Pallet<T, I> {
27 pub(crate) fn do_set_attribute(
51 origin: T::AccountId,
52 collection: T::CollectionId,
53 maybe_item: Option<T::ItemId>,
54 namespace: AttributeNamespace<T::AccountId>,
55 key: BoundedVec<u8, T::KeyLimit>,
56 value: BoundedVec<u8, T::ValueLimit>,
57 depositor: T::AccountId,
58 ) -> DispatchResult {
59 ensure!(
60 Self::is_pallet_feature_enabled(PalletFeature::Attributes),
61 Error::<T, I>::MethodDisabled
62 );
63
64 ensure!(
65 Self::is_valid_namespace(&origin, &namespace, &collection, &maybe_item)?,
66 Error::<T, I>::NoPermission
67 );
68
69 let collection_config = Self::get_collection_config(&collection)?;
70 match namespace {
72 AttributeNamespace::CollectionOwner => match maybe_item {
73 None => {
74 ensure!(
75 collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes),
76 Error::<T, I>::LockedCollectionAttributes
77 )
78 },
79 Some(item) => {
80 let maybe_is_locked = Self::get_item_config(&collection, &item)
81 .map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?;
82 ensure!(!maybe_is_locked, Error::<T, I>::LockedItemAttributes);
83 },
84 },
85 _ => (),
86 }
87
88 let mut collection_details =
89 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
90
91 let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
92 let attribute_exists = attribute.is_some();
93 if !attribute_exists {
94 collection_details.attributes.saturating_inc();
95 }
96
97 let old_deposit =
98 attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1);
99
100 let mut deposit = Zero::zero();
101 if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) ||
103 namespace != AttributeNamespace::CollectionOwner
104 {
105 deposit = T::DepositPerByte::get()
106 .saturating_mul(((key.len() + value.len()) as u32).into())
107 .saturating_add(T::AttributeDepositBase::get());
108 }
109
110 let is_collection_owner_namespace = namespace == AttributeNamespace::CollectionOwner;
111 let is_depositor_collection_owner =
112 is_collection_owner_namespace && collection_details.owner == depositor;
113
114 let old_depositor =
117 if is_collection_owner_namespace && old_deposit.account.is_none() && attribute_exists {
118 Some(collection_details.owner.clone())
119 } else {
120 old_deposit.account
121 };
122 let depositor_has_changed = old_depositor != Some(depositor.clone());
123
124 if depositor_has_changed {
128 if let Some(old_depositor) = old_depositor {
129 T::Currency::unreserve(&old_depositor, old_deposit.amount);
130 }
131 T::Currency::reserve(&depositor, deposit)?;
132 } else if deposit > old_deposit.amount {
133 T::Currency::reserve(&depositor, deposit - old_deposit.amount)?;
134 } else if deposit < old_deposit.amount {
135 T::Currency::unreserve(&depositor, old_deposit.amount - deposit);
136 }
137
138 if is_depositor_collection_owner {
139 if !depositor_has_changed {
140 collection_details.owner_deposit.saturating_reduce(old_deposit.amount);
141 }
142 collection_details.owner_deposit.saturating_accrue(deposit);
143 }
144
145 let new_deposit_owner = match is_depositor_collection_owner {
146 true => None,
147 false => Some(depositor),
148 };
149 Attribute::<T, I>::insert(
150 (&collection, maybe_item, &namespace, &key),
151 (&value, AttributeDeposit { account: new_deposit_owner, amount: deposit }),
152 );
153
154 Collection::<T, I>::insert(collection, &collection_details);
155 Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
156 Ok(())
157 }
158
159 pub(crate) fn do_force_set_attribute(
177 set_as: Option<T::AccountId>,
178 collection: T::CollectionId,
179 maybe_item: Option<T::ItemId>,
180 namespace: AttributeNamespace<T::AccountId>,
181 key: BoundedVec<u8, T::KeyLimit>,
182 value: BoundedVec<u8, T::ValueLimit>,
183 ) -> DispatchResult {
184 let mut collection_details =
185 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
186
187 let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
188 if let Some((_, deposit)) = attribute {
189 if deposit.account != set_as && deposit.amount != Zero::zero() {
190 if let Some(deposit_account) = deposit.account {
191 T::Currency::unreserve(&deposit_account, deposit.amount);
192 }
193 }
194 } else {
195 collection_details.attributes.saturating_inc();
196 }
197
198 Attribute::<T, I>::insert(
199 (&collection, maybe_item, &namespace, &key),
200 (&value, AttributeDeposit { account: set_as, amount: Zero::zero() }),
201 );
202 Collection::<T, I>::insert(collection, &collection_details);
203 Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
204 Ok(())
205 }
206
207 pub(crate) fn do_set_attributes_pre_signed(
217 origin: T::AccountId,
218 data: PreSignedAttributesOf<T, I>,
219 signer: T::AccountId,
220 ) -> DispatchResult {
221 let PreSignedAttributes { collection, item, attributes, namespace, deadline } = data;
222
223 ensure!(
224 attributes.len() <= T::MaxAttributesPerCall::get() as usize,
225 Error::<T, I>::MaxAttributesLimitReached
226 );
227
228 let now = T::BlockNumberProvider::current_block_number();
229 ensure!(deadline >= now, Error::<T, I>::DeadlineExpired);
230
231 let item_details =
232 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
233 ensure!(item_details.owner == origin, Error::<T, I>::NoPermission);
234
235 match &namespace {
238 AttributeNamespace::CollectionOwner => {},
239 AttributeNamespace::Account(account) => {
240 ensure!(account == &signer, Error::<T, I>::NoPermission);
241 let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
242 if !approvals.contains(account) {
243 Self::do_approve_item_attributes(
244 origin.clone(),
245 collection,
246 item,
247 account.clone(),
248 )?;
249 }
250 },
251 _ => return Err(Error::<T, I>::WrongNamespace.into()),
252 }
253
254 for (key, value) in attributes {
255 Self::do_set_attribute(
256 signer.clone(),
257 collection,
258 Some(item),
259 namespace.clone(),
260 Self::construct_attribute_key(key)?,
261 Self::construct_attribute_value(value)?,
262 origin.clone(),
263 )?;
264 }
265 Self::deposit_event(Event::PreSignedAttributesSet { collection, item, namespace });
266 Ok(())
267 }
268
269 pub(crate) fn do_clear_attribute(
286 maybe_check_origin: Option<T::AccountId>,
287 collection: T::CollectionId,
288 maybe_item: Option<T::ItemId>,
289 namespace: AttributeNamespace<T::AccountId>,
290 key: BoundedVec<u8, T::KeyLimit>,
291 ) -> DispatchResult {
292 let (_, deposit) = Attribute::<T, I>::take((collection, maybe_item, &namespace, &key))
293 .ok_or(Error::<T, I>::AttributeNotFound)?;
294
295 if let Some(check_origin) = &maybe_check_origin {
296 if deposit.account != maybe_check_origin {
299 ensure!(
300 Self::is_valid_namespace(&check_origin, &namespace, &collection, &maybe_item)?,
301 Error::<T, I>::NoPermission
302 );
303 }
304
305 match namespace {
307 AttributeNamespace::CollectionOwner => match maybe_item {
308 None => {
309 let collection_config = Self::get_collection_config(&collection)?;
310 ensure!(
311 collection_config
312 .is_setting_enabled(CollectionSetting::UnlockedAttributes),
313 Error::<T, I>::LockedCollectionAttributes
314 )
315 },
316 Some(item) => {
317 let maybe_is_locked = Self::get_item_config(&collection, &item)
320 .map_or(None, |c| {
321 Some(c.has_disabled_setting(ItemSetting::UnlockedAttributes))
322 });
323 if let Some(is_locked) = maybe_is_locked {
324 ensure!(!is_locked, Error::<T, I>::LockedItemAttributes);
325 ensure!(
329 Self::has_role(&collection, &check_origin, CollectionRole::Admin),
330 Error::<T, I>::NoPermission
331 );
332 }
333 },
334 },
335 _ => (),
336 };
337 }
338
339 let mut collection_details =
340 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
341
342 collection_details.attributes.saturating_dec();
343
344 match deposit.account {
345 Some(deposit_account) => {
346 T::Currency::unreserve(&deposit_account, deposit.amount);
347 },
348 None if namespace == AttributeNamespace::CollectionOwner => {
349 collection_details.owner_deposit.saturating_reduce(deposit.amount);
350 T::Currency::unreserve(&collection_details.owner, deposit.amount);
351 },
352 _ => (),
353 }
354
355 Collection::<T, I>::insert(collection, &collection_details);
356 Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace });
357
358 Ok(())
359 }
360
361 pub(crate) fn do_approve_item_attributes(
373 check_origin: T::AccountId,
374 collection: T::CollectionId,
375 item: T::ItemId,
376 delegate: T::AccountId,
377 ) -> DispatchResult {
378 ensure!(
379 Self::is_pallet_feature_enabled(PalletFeature::Attributes),
380 Error::<T, I>::MethodDisabled
381 );
382
383 let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
384 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
385
386 ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
387 approvals
388 .try_insert(delegate.clone())
389 .map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
390
391 Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate });
392 Ok(())
393 })
394 }
395
396 pub(crate) fn do_cancel_item_attributes_approval(
413 check_origin: T::AccountId,
414 collection: T::CollectionId,
415 item: T::ItemId,
416 delegate: T::AccountId,
417 witness: CancelAttributesApprovalWitness,
418 ) -> DispatchResult {
419 ensure!(
420 Self::is_pallet_feature_enabled(PalletFeature::Attributes),
421 Error::<T, I>::MethodDisabled
422 );
423
424 let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
425 ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
426
427 ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
428 approvals.remove(&delegate);
429
430 let mut attributes: u32 = 0;
431 let mut deposited: DepositBalanceOf<T, I> = Zero::zero();
432 for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((
433 &collection,
434 Some(item),
435 AttributeNamespace::Account(delegate.clone()),
436 )) {
437 attributes.saturating_inc();
438 deposited = deposited.saturating_add(deposit.amount);
439 }
440 ensure!(attributes <= witness.account_attributes, Error::<T, I>::BadWitness);
441
442 if !deposited.is_zero() {
443 T::Currency::unreserve(&delegate, deposited);
444 }
445
446 Self::deposit_event(Event::ItemAttributesApprovalRemoved {
447 collection,
448 item,
449 delegate,
450 });
451 Ok(())
452 })
453 }
454
455 fn is_valid_namespace(
457 origin: &T::AccountId,
458 namespace: &AttributeNamespace<T::AccountId>,
459 collection: &T::CollectionId,
460 maybe_item: &Option<T::ItemId>,
461 ) -> Result<bool, DispatchError> {
462 let mut result = false;
463 match namespace {
464 AttributeNamespace::CollectionOwner =>
465 result = Self::has_role(&collection, &origin, CollectionRole::Admin),
466 AttributeNamespace::ItemOwner =>
467 if let Some(item) = maybe_item {
468 let item_details =
469 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
470 result = origin == &item_details.owner
471 },
472 AttributeNamespace::Account(account_id) =>
473 if let Some(item) = maybe_item {
474 let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
475 result = account_id == origin && approvals.contains(&origin)
476 },
477 _ => (),
478 };
479 Ok(result)
480 }
481
482 pub fn construct_attribute_key(
489 key: Vec<u8>,
490 ) -> Result<BoundedVec<u8, T::KeyLimit>, DispatchError> {
491 Ok(BoundedVec::try_from(key).map_err(|_| Error::<T, I>::IncorrectData)?)
492 }
493
494 pub fn construct_attribute_value(
501 value: Vec<u8>,
502 ) -> Result<BoundedVec<u8, T::ValueLimit>, DispatchError> {
503 Ok(BoundedVec::try_from(value).map_err(|_| Error::<T, I>::IncorrectData)?)
504 }
505
506 pub fn has_system_attribute(
513 collection: &T::CollectionId,
514 item: &T::ItemId,
515 attribute_key: PalletAttributes<T::CollectionId>,
516 ) -> Result<bool, DispatchError> {
517 let attribute = (
518 &collection,
519 Some(item),
520 AttributeNamespace::Pallet,
521 &Self::construct_attribute_key(attribute_key.encode())?,
522 );
523 Ok(Attribute::<T, I>::contains_key(attribute))
524 }
525}