1#![recursion_limit = "256"]
28#![cfg_attr(not(feature = "std"), no_std)]
30
31#[cfg(feature = "runtime-benchmarks")]
32mod benchmarking;
33#[cfg(test)]
34pub mod mock;
35#[cfg(test)]
36mod tests;
37
38mod functions;
39mod impl_nonfungibles;
40mod types;
41
42pub mod asset_ops;
43pub mod migration;
44pub mod weights;
45
46extern crate alloc;
47
48use alloc::vec::Vec;
49use codec::{Decode, DecodeWithMemTracking, Encode};
50use frame_support::traits::{
51 tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
52};
53use frame_system::Config as SystemConfig;
54use sp_runtime::{
55 traits::{Saturating, StaticLookup, Zero},
56 ArithmeticError, Debug,
57};
58
59pub use pallet::*;
60pub use types::*;
61pub use weights::WeightInfo;
62
63const LOG_TARGET: &str = "runtime::uniques";
65
66type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
68
69#[frame_support::pallet]
70pub mod pallet {
71 use super::*;
72 use frame_support::pallet_prelude::*;
73 use frame_system::pallet_prelude::*;
74
75 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
76
77 #[pallet::pallet]
78 #[pallet::storage_version(STORAGE_VERSION)]
79 pub struct Pallet<T, I = ()>(_);
80
81 #[cfg(feature = "runtime-benchmarks")]
82 pub trait BenchmarkHelper<CollectionId, ItemId> {
83 fn collection(i: u16) -> CollectionId;
84 fn item(i: u16) -> ItemId;
85 }
86 #[cfg(feature = "runtime-benchmarks")]
87 impl<CollectionId: From<u16>, ItemId: From<u16>> BenchmarkHelper<CollectionId, ItemId> for () {
88 fn collection(i: u16) -> CollectionId {
89 i.into()
90 }
91 fn item(i: u16) -> ItemId {
92 i.into()
93 }
94 }
95
96 #[pallet::config]
97 pub trait Config<I: 'static = ()>: frame_system::Config {
99 #[allow(deprecated)]
101 type RuntimeEvent: From<Event<Self, I>>
102 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
103
104 type CollectionId: Member + Parameter + MaxEncodedLen;
106
107 type ItemId: Member + Parameter + MaxEncodedLen + Copy;
109
110 type Currency: ReservableCurrency<Self::AccountId>;
112
113 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
116
117 type CreateOrigin: EnsureOriginWithArg<
120 Self::RuntimeOrigin,
121 Self::CollectionId,
122 Success = Self::AccountId,
123 >;
124
125 type Locker: Locker<Self::CollectionId, Self::ItemId>;
127
128 #[pallet::constant]
130 type CollectionDeposit: Get<DepositBalanceOf<Self, I>>;
131
132 #[pallet::constant]
134 type ItemDeposit: Get<DepositBalanceOf<Self, I>>;
135
136 #[pallet::constant]
138 type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
139
140 #[pallet::constant]
142 type AttributeDepositBase: Get<DepositBalanceOf<Self, I>>;
143
144 #[pallet::constant]
147 type DepositPerByte: Get<DepositBalanceOf<Self, I>>;
148
149 #[pallet::constant]
151 type StringLimit: Get<u32>;
152
153 #[pallet::constant]
155 type KeyLimit: Get<u32>;
156
157 #[pallet::constant]
159 type ValueLimit: Get<u32>;
160
161 #[cfg(feature = "runtime-benchmarks")]
162 type Helper: BenchmarkHelper<Self::CollectionId, Self::ItemId>;
164
165 type WeightInfo: WeightInfo;
167 }
168
169 #[pallet::storage]
170 #[pallet::storage_prefix = "Class"]
171 pub type Collection<T: Config<I>, I: 'static = ()> = StorageMap<
173 _,
174 Blake2_128Concat,
175 T::CollectionId,
176 CollectionDetails<T::AccountId, DepositBalanceOf<T, I>>,
177 >;
178
179 #[pallet::storage]
180 pub type OwnershipAcceptance<T: Config<I>, I: 'static = ()> =
182 StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>;
183
184 #[pallet::storage]
185 pub type Account<T: Config<I>, I: 'static = ()> = StorageNMap<
188 _,
189 (
190 NMapKey<Blake2_128Concat, T::AccountId>, NMapKey<Blake2_128Concat, T::CollectionId>,
192 NMapKey<Blake2_128Concat, T::ItemId>,
193 ),
194 (),
195 OptionQuery,
196 >;
197
198 #[pallet::storage]
199 #[pallet::storage_prefix = "ClassAccount"]
200 pub type CollectionAccount<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
203 _,
204 Blake2_128Concat,
205 T::AccountId,
206 Blake2_128Concat,
207 T::CollectionId,
208 (),
209 OptionQuery,
210 >;
211
212 #[pallet::storage]
213 #[pallet::storage_prefix = "Asset"]
214 pub type Item<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
216 _,
217 Blake2_128Concat,
218 T::CollectionId,
219 Blake2_128Concat,
220 T::ItemId,
221 ItemDetails<T::AccountId, DepositBalanceOf<T, I>>,
222 OptionQuery,
223 >;
224
225 #[pallet::storage]
226 #[pallet::storage_prefix = "ClassMetadataOf"]
227 pub type CollectionMetadataOf<T: Config<I>, I: 'static = ()> = StorageMap<
229 _,
230 Blake2_128Concat,
231 T::CollectionId,
232 CollectionMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
233 OptionQuery,
234 >;
235
236 #[pallet::storage]
237 #[pallet::storage_prefix = "InstanceMetadataOf"]
238 pub type ItemMetadataOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
240 _,
241 Blake2_128Concat,
242 T::CollectionId,
243 Blake2_128Concat,
244 T::ItemId,
245 ItemMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
246 OptionQuery,
247 >;
248
249 #[pallet::storage]
250 pub type Attribute<T: Config<I>, I: 'static = ()> = StorageNMap<
252 _,
253 (
254 NMapKey<Blake2_128Concat, T::CollectionId>,
255 NMapKey<Blake2_128Concat, Option<T::ItemId>>,
256 NMapKey<Blake2_128Concat, BoundedVec<u8, T::KeyLimit>>,
257 ),
258 (BoundedVec<u8, T::ValueLimit>, DepositBalanceOf<T, I>),
259 OptionQuery,
260 >;
261
262 #[pallet::storage]
263 pub type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
265 _,
266 Blake2_128Concat,
267 T::CollectionId,
268 Blake2_128Concat,
269 T::ItemId,
270 (ItemPrice<T, I>, Option<T::AccountId>),
271 OptionQuery,
272 >;
273
274 #[pallet::storage]
275 pub type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
277 StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>;
278
279 #[pallet::event]
280 #[pallet::generate_deposit(pub(super) fn deposit_event)]
281 pub enum Event<T: Config<I>, I: 'static = ()> {
282 Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId },
284 ForceCreated { collection: T::CollectionId, owner: T::AccountId },
286 Destroyed { collection: T::CollectionId },
288 Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
290 Transferred {
292 collection: T::CollectionId,
293 item: T::ItemId,
294 from: T::AccountId,
295 to: T::AccountId,
296 },
297 Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
299 Frozen { collection: T::CollectionId, item: T::ItemId },
301 Thawed { collection: T::CollectionId, item: T::ItemId },
303 CollectionFrozen { collection: T::CollectionId },
305 CollectionThawed { collection: T::CollectionId },
307 OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId },
309 TeamChanged {
311 collection: T::CollectionId,
312 issuer: T::AccountId,
313 admin: T::AccountId,
314 freezer: T::AccountId,
315 },
316 ApprovedTransfer {
319 collection: T::CollectionId,
320 item: T::ItemId,
321 owner: T::AccountId,
322 delegate: T::AccountId,
323 },
324 ApprovalCancelled {
327 collection: T::CollectionId,
328 item: T::ItemId,
329 owner: T::AccountId,
330 delegate: T::AccountId,
331 },
332 ItemStatusChanged { collection: T::CollectionId },
334 CollectionMetadataSet {
336 collection: T::CollectionId,
337 data: BoundedVec<u8, T::StringLimit>,
338 is_frozen: bool,
339 },
340 CollectionMetadataCleared { collection: T::CollectionId },
342 MetadataSet {
344 collection: T::CollectionId,
345 item: T::ItemId,
346 data: BoundedVec<u8, T::StringLimit>,
347 is_frozen: bool,
348 },
349 MetadataCleared { collection: T::CollectionId, item: T::ItemId },
351 Redeposited { collection: T::CollectionId, successful_items: Vec<T::ItemId> },
353 AttributeSet {
355 collection: T::CollectionId,
356 maybe_item: Option<T::ItemId>,
357 key: BoundedVec<u8, T::KeyLimit>,
358 value: BoundedVec<u8, T::ValueLimit>,
359 },
360 AttributeCleared {
362 collection: T::CollectionId,
363 maybe_item: Option<T::ItemId>,
364 key: BoundedVec<u8, T::KeyLimit>,
365 },
366 OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
368 CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
370 ItemPriceSet {
372 collection: T::CollectionId,
373 item: T::ItemId,
374 price: ItemPrice<T, I>,
375 whitelisted_buyer: Option<T::AccountId>,
376 },
377 ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
379 ItemBought {
381 collection: T::CollectionId,
382 item: T::ItemId,
383 price: ItemPrice<T, I>,
384 seller: T::AccountId,
385 buyer: T::AccountId,
386 },
387 }
388
389 #[pallet::error]
390 pub enum Error<T, I = ()> {
391 NoPermission,
393 UnknownCollection,
395 AlreadyExists,
397 WrongOwner,
399 BadWitness,
401 InUse,
403 Frozen,
405 WrongDelegate,
407 NoDelegate,
409 Unapproved,
411 Unaccepted,
413 Locked,
415 MaxSupplyReached,
417 MaxSupplyAlreadySet,
419 MaxSupplyTooSmall,
421 UnknownItem,
423 NotForSale,
425 BidTooLow,
427 NoMetadata,
429 WrongMetadata,
431 AttributeNotFound,
433 WrongAttribute,
435 IncompleteDepositTransfer,
437 }
438
439 impl<T: Config<I>, I: 'static> Pallet<T, I> {
440 pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
442 Item::<T, I>::get(collection, item).map(|i| i.owner)
443 }
444
445 pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
447 Collection::<T, I>::get(collection).map(|i| i.owner)
448 }
449 }
450
451 #[pallet::call]
452 impl<T: Config<I>, I: 'static> Pallet<T, I> {
453 #[pallet::call_index(0)]
470 #[pallet::weight(T::WeightInfo::create())]
471 pub fn create(
472 origin: OriginFor<T>,
473 collection: T::CollectionId,
474 admin: AccountIdLookupOf<T>,
475 ) -> DispatchResult {
476 let owner = T::CreateOrigin::ensure_origin(origin, &collection)?;
477 let admin = T::Lookup::lookup(admin)?;
478
479 Self::do_create_collection(
480 collection.clone(),
481 owner.clone(),
482 admin.clone(),
483 T::CollectionDeposit::get(),
484 false,
485 Event::Created { collection, creator: owner, owner: admin },
486 )
487 }
488
489 #[pallet::call_index(1)]
507 #[pallet::weight(T::WeightInfo::force_create())]
508 pub fn force_create(
509 origin: OriginFor<T>,
510 collection: T::CollectionId,
511 owner: AccountIdLookupOf<T>,
512 free_holding: bool,
513 ) -> DispatchResult {
514 T::ForceOrigin::ensure_origin(origin)?;
515 let owner = T::Lookup::lookup(owner)?;
516
517 Self::do_create_collection(
518 collection.clone(),
519 owner.clone(),
520 owner.clone(),
521 Zero::zero(),
522 free_holding,
523 Event::ForceCreated { collection, owner },
524 )
525 }
526
527 #[pallet::call_index(2)]
543 #[pallet::weight(T::WeightInfo::destroy(
544 witness.items,
545 witness.item_metadatas,
546 witness.attributes,
547 ))]
548 pub fn destroy(
549 origin: OriginFor<T>,
550 collection: T::CollectionId,
551 witness: DestroyWitness,
552 ) -> DispatchResultWithPostInfo {
553 let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
554 Ok(_) => None,
555 Err(origin) => Some(ensure_signed(origin)?),
556 };
557 let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
558
559 Ok(Some(T::WeightInfo::destroy(
560 details.items,
561 details.item_metadatas,
562 details.attributes,
563 ))
564 .into())
565 }
566
567 #[pallet::call_index(3)]
579 #[pallet::weight(T::WeightInfo::mint())]
580 pub fn mint(
581 origin: OriginFor<T>,
582 collection: T::CollectionId,
583 item: T::ItemId,
584 owner: AccountIdLookupOf<T>,
585 ) -> DispatchResult {
586 let origin = ensure_signed(origin)?;
587 let owner = T::Lookup::lookup(owner)?;
588
589 Self::do_mint(collection, item, owner, |collection_details| {
590 ensure!(collection_details.issuer == origin, Error::<T, I>::NoPermission);
591 Ok(())
592 })
593 }
594
595 #[pallet::call_index(4)]
611 #[pallet::weight(T::WeightInfo::burn())]
612 pub fn burn(
613 origin: OriginFor<T>,
614 collection: T::CollectionId,
615 item: T::ItemId,
616 check_owner: Option<AccountIdLookupOf<T>>,
617 ) -> DispatchResult {
618 let origin = ensure_signed(origin)?;
619 let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
620
621 Self::do_burn(collection, item, |collection_details, details| {
622 let is_permitted = collection_details.admin == origin || details.owner == origin;
623 ensure!(is_permitted, Error::<T, I>::NoPermission);
624 ensure!(
625 check_owner.map_or(true, |o| o == details.owner),
626 Error::<T, I>::WrongOwner
627 );
628 Ok(())
629 })
630 }
631
632 #[pallet::call_index(5)]
650 #[pallet::weight(T::WeightInfo::transfer())]
651 pub fn transfer(
652 origin: OriginFor<T>,
653 collection: T::CollectionId,
654 item: T::ItemId,
655 dest: AccountIdLookupOf<T>,
656 ) -> DispatchResult {
657 let origin = ensure_signed(origin)?;
658 let dest = T::Lookup::lookup(dest)?;
659
660 Self::do_transfer(collection, item, dest, |collection_details, details| {
661 if details.owner != origin && collection_details.admin != origin {
662 let approved = details.approved.take().map_or(false, |i| i == origin);
663 ensure!(approved, Error::<T, I>::NoPermission);
664 }
665 Ok(())
666 })
667 }
668
669 #[pallet::call_index(6)]
687 #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))]
688 pub fn redeposit(
689 origin: OriginFor<T>,
690 collection: T::CollectionId,
691 items: Vec<T::ItemId>,
692 ) -> DispatchResult {
693 let origin = ensure_signed(origin)?;
694
695 let mut collection_details =
696 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
697 ensure!(collection_details.owner == origin, Error::<T, I>::NoPermission);
698 let deposit = match collection_details.free_holding {
699 true => Zero::zero(),
700 false => T::ItemDeposit::get(),
701 };
702
703 let mut successful = Vec::with_capacity(items.len());
704 for item in items.into_iter() {
705 let mut details = match Item::<T, I>::get(&collection, &item) {
706 Some(x) => x,
707 None => continue,
708 };
709 let old = details.deposit;
710 if old > deposit {
711 T::Currency::unreserve(&collection_details.owner, old - deposit);
712 } else if deposit > old {
713 if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() {
714 continue;
717 }
718 } else {
719 continue;
720 }
721 collection_details.total_deposit.saturating_accrue(deposit);
722 collection_details.total_deposit.saturating_reduce(old);
723 details.deposit = deposit;
724 Item::<T, I>::insert(&collection, &item, &details);
725 successful.push(item);
726 }
727 Collection::<T, I>::insert(&collection, &collection_details);
728
729 Self::deposit_event(Event::<T, I>::Redeposited {
730 collection,
731 successful_items: successful,
732 });
733
734 Ok(())
735 }
736
737 #[pallet::call_index(7)]
748 #[pallet::weight(T::WeightInfo::freeze())]
749 pub fn freeze(
750 origin: OriginFor<T>,
751 collection: T::CollectionId,
752 item: T::ItemId,
753 ) -> DispatchResult {
754 let origin = ensure_signed(origin)?;
755
756 let mut details =
757 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
758 let collection_details =
759 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
760 ensure!(collection_details.freezer == origin, Error::<T, I>::NoPermission);
761
762 details.is_frozen = true;
763 Item::<T, I>::insert(&collection, &item, &details);
764
765 Self::deposit_event(Event::<T, I>::Frozen { collection, item });
766 Ok(())
767 }
768
769 #[pallet::call_index(8)]
780 #[pallet::weight(T::WeightInfo::thaw())]
781 pub fn thaw(
782 origin: OriginFor<T>,
783 collection: T::CollectionId,
784 item: T::ItemId,
785 ) -> DispatchResult {
786 let origin = ensure_signed(origin)?;
787
788 let mut details =
789 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
790 let collection_details =
791 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
792 ensure!(collection_details.admin == origin, Error::<T, I>::NoPermission);
793
794 details.is_frozen = false;
795 Item::<T, I>::insert(&collection, &item, &details);
796
797 Self::deposit_event(Event::<T, I>::Thawed { collection, item });
798 Ok(())
799 }
800
801 #[pallet::call_index(9)]
811 #[pallet::weight(T::WeightInfo::freeze_collection())]
812 pub fn freeze_collection(
813 origin: OriginFor<T>,
814 collection: T::CollectionId,
815 ) -> DispatchResult {
816 let origin = ensure_signed(origin)?;
817
818 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
819 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
820 ensure!(origin == details.freezer, Error::<T, I>::NoPermission);
821
822 details.is_frozen = true;
823
824 Self::deposit_event(Event::<T, I>::CollectionFrozen { collection });
825 Ok(())
826 })
827 }
828
829 #[pallet::call_index(10)]
839 #[pallet::weight(T::WeightInfo::thaw_collection())]
840 pub fn thaw_collection(
841 origin: OriginFor<T>,
842 collection: T::CollectionId,
843 ) -> DispatchResult {
844 let origin = ensure_signed(origin)?;
845
846 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
847 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
848 ensure!(origin == details.admin, Error::<T, I>::NoPermission);
849
850 details.is_frozen = false;
851
852 Self::deposit_event(Event::<T, I>::CollectionThawed { collection });
853 Ok(())
854 })
855 }
856
857 #[pallet::call_index(11)]
873 #[pallet::weight(T::WeightInfo::transfer_ownership())]
874 pub fn transfer_ownership(
875 origin: OriginFor<T>,
876 collection: T::CollectionId,
877 new_owner: AccountIdLookupOf<T>,
878 ) -> DispatchResult {
879 let origin = ensure_signed(origin)?;
880 let new_owner = T::Lookup::lookup(new_owner)?;
881
882 let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
883 ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
884
885 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
886 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
887 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
888 if details.owner == new_owner {
889 return Ok(());
890 }
891
892 let remaining = T::Currency::repatriate_reserved(
896 &details.owner,
897 &new_owner,
898 details.total_deposit,
899 Reserved,
900 )?;
901 ensure!(remaining.is_zero(), Error::<T, I>::IncompleteDepositTransfer);
902
903 CollectionAccount::<T, I>::remove(&details.owner, &collection);
904 CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
905
906 details.owner = new_owner.clone();
907 OwnershipAcceptance::<T, I>::remove(&new_owner);
908 frame_system::Pallet::<T>::dec_consumers(&new_owner);
909
910 Self::deposit_event(Event::OwnerChanged { collection, new_owner });
911 Ok(())
912 })
913 }
914
915 #[pallet::call_index(12)]
928 #[pallet::weight(T::WeightInfo::set_team())]
929 pub fn set_team(
930 origin: OriginFor<T>,
931 collection: T::CollectionId,
932 issuer: AccountIdLookupOf<T>,
933 admin: AccountIdLookupOf<T>,
934 freezer: AccountIdLookupOf<T>,
935 ) -> DispatchResult {
936 let origin = ensure_signed(origin)?;
937 let issuer = T::Lookup::lookup(issuer)?;
938 let admin = T::Lookup::lookup(admin)?;
939 let freezer = T::Lookup::lookup(freezer)?;
940
941 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
942 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
943 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
944
945 details.issuer = issuer.clone();
946 details.admin = admin.clone();
947 details.freezer = freezer.clone();
948
949 Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
950 Ok(())
951 })
952 }
953
954 #[pallet::call_index(13)]
969 #[pallet::weight(T::WeightInfo::approve_transfer())]
970 pub fn approve_transfer(
971 origin: OriginFor<T>,
972 collection: T::CollectionId,
973 item: T::ItemId,
974 delegate: AccountIdLookupOf<T>,
975 ) -> DispatchResult {
976 let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
977 .map(|_| None)
978 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
979
980 let delegate = T::Lookup::lookup(delegate)?;
981
982 let collection_details =
983 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
984 let mut details =
985 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
986
987 if let Some(check) = maybe_check {
988 let permitted = check == collection_details.admin || check == details.owner;
989 ensure!(permitted, Error::<T, I>::NoPermission);
990 }
991
992 details.approved = Some(delegate);
993 Item::<T, I>::insert(&collection, &item, &details);
994
995 let delegate = details.approved.expect("set as Some above; qed");
996 Self::deposit_event(Event::ApprovedTransfer {
997 collection,
998 item,
999 owner: details.owner,
1000 delegate,
1001 });
1002
1003 Ok(())
1004 }
1005
1006 #[pallet::call_index(14)]
1023 #[pallet::weight(T::WeightInfo::cancel_approval())]
1024 pub fn cancel_approval(
1025 origin: OriginFor<T>,
1026 collection: T::CollectionId,
1027 item: T::ItemId,
1028 maybe_check_delegate: Option<AccountIdLookupOf<T>>,
1029 ) -> DispatchResult {
1030 let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
1031 .map(|_| None)
1032 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1033
1034 let collection_details =
1035 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1036 let mut details =
1037 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
1038 if let Some(check) = maybe_check {
1039 let permitted = check == collection_details.admin || check == details.owner;
1040 ensure!(permitted, Error::<T, I>::NoPermission);
1041 }
1042 let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?;
1043 let old = details.approved.take().ok_or(Error::<T, I>::NoDelegate)?;
1044 if let Some(check_delegate) = maybe_check_delegate {
1045 ensure!(check_delegate == old, Error::<T, I>::WrongDelegate);
1046 }
1047
1048 Item::<T, I>::insert(&collection, &item, &details);
1049 Self::deposit_event(Event::ApprovalCancelled {
1050 collection,
1051 item,
1052 owner: details.owner,
1053 delegate: old,
1054 });
1055
1056 Ok(())
1057 }
1058
1059 #[pallet::call_index(15)]
1076 #[pallet::weight(T::WeightInfo::force_item_status())]
1077 pub fn force_item_status(
1078 origin: OriginFor<T>,
1079 collection: T::CollectionId,
1080 owner: AccountIdLookupOf<T>,
1081 issuer: AccountIdLookupOf<T>,
1082 admin: AccountIdLookupOf<T>,
1083 freezer: AccountIdLookupOf<T>,
1084 free_holding: bool,
1085 is_frozen: bool,
1086 ) -> DispatchResult {
1087 T::ForceOrigin::ensure_origin(origin)?;
1088
1089 Collection::<T, I>::try_mutate(collection.clone(), |maybe_item| {
1090 let mut item = maybe_item.take().ok_or(Error::<T, I>::UnknownCollection)?;
1091 let old_owner = item.owner;
1092 let new_owner = T::Lookup::lookup(owner)?;
1093 item.owner = new_owner.clone();
1094 item.issuer = T::Lookup::lookup(issuer)?;
1095 item.admin = T::Lookup::lookup(admin)?;
1096 item.freezer = T::Lookup::lookup(freezer)?;
1097 item.free_holding = free_holding;
1098 item.is_frozen = is_frozen;
1099 *maybe_item = Some(item);
1100 CollectionAccount::<T, I>::remove(&old_owner, &collection);
1101 CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
1102
1103 Self::deposit_event(Event::ItemStatusChanged { collection });
1104 Ok(())
1105 })
1106 }
1107
1108 #[pallet::call_index(16)]
1126 #[pallet::weight(T::WeightInfo::set_attribute())]
1127 pub fn set_attribute(
1128 origin: OriginFor<T>,
1129 collection: T::CollectionId,
1130 maybe_item: Option<T::ItemId>,
1131 key: BoundedVec<u8, T::KeyLimit>,
1132 value: BoundedVec<u8, T::ValueLimit>,
1133 ) -> DispatchResult {
1134 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1135 .map(|_| None)
1136 .or_else(|origin| ensure_signed(origin).map(Some))?;
1137
1138 let mut collection_details =
1139 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1140 if let Some(check_owner) = &maybe_check_owner {
1141 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1142 }
1143 let maybe_is_frozen = match maybe_item {
1144 None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1145 Some(item) => {
1146 ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen)
1147 },
1148 };
1149 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1150
1151 let attribute = Attribute::<T, I>::get((collection.clone(), maybe_item, &key));
1152 if attribute.is_none() {
1153 collection_details.attributes.saturating_inc();
1154 }
1155 let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
1156 collection_details.total_deposit.saturating_reduce(old_deposit);
1157 let mut deposit = Zero::zero();
1158 if !collection_details.free_holding && maybe_check_owner.is_some() {
1159 deposit = T::DepositPerByte::get()
1160 .saturating_mul(((key.len() + value.len()) as u32).into())
1161 .saturating_add(T::AttributeDepositBase::get());
1162 }
1163 collection_details.total_deposit.saturating_accrue(deposit);
1164 if deposit > old_deposit {
1165 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1166 } else if deposit < old_deposit {
1167 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1168 }
1169
1170 Attribute::<T, I>::insert((&collection, maybe_item, &key), (&value, deposit));
1171 Collection::<T, I>::insert(collection.clone(), &collection_details);
1172 Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value });
1173 Ok(())
1174 }
1175
1176 #[pallet::call_index(17)]
1191 #[pallet::weight(T::WeightInfo::clear_attribute())]
1192 pub fn clear_attribute(
1193 origin: OriginFor<T>,
1194 collection: T::CollectionId,
1195 maybe_item: Option<T::ItemId>,
1196 key: BoundedVec<u8, T::KeyLimit>,
1197 ) -> DispatchResult {
1198 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1199 .map(|_| None)
1200 .or_else(|origin| ensure_signed(origin).map(Some))?;
1201
1202 let mut collection_details =
1203 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1204 if let Some(check_owner) = &maybe_check_owner {
1205 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1206 }
1207 let maybe_is_frozen = match maybe_item {
1208 None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1209 Some(item) => {
1210 ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen)
1211 },
1212 };
1213 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1214
1215 if let Some((_, deposit)) =
1216 Attribute::<T, I>::take((collection.clone(), maybe_item, &key))
1217 {
1218 collection_details.attributes.saturating_dec();
1219 collection_details.total_deposit.saturating_reduce(deposit);
1220 T::Currency::unreserve(&collection_details.owner, deposit);
1221 Collection::<T, I>::insert(collection.clone(), &collection_details);
1222 Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key });
1223 }
1224 Ok(())
1225 }
1226
1227 #[pallet::call_index(18)]
1245 #[pallet::weight(T::WeightInfo::set_metadata())]
1246 pub fn set_metadata(
1247 origin: OriginFor<T>,
1248 collection: T::CollectionId,
1249 item: T::ItemId,
1250 data: BoundedVec<u8, T::StringLimit>,
1251 is_frozen: bool,
1252 ) -> DispatchResult {
1253 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1254 .map(|_| None)
1255 .or_else(|origin| ensure_signed(origin).map(Some))?;
1256
1257 let mut collection_details =
1258 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1259
1260 if let Some(check_owner) = &maybe_check_owner {
1261 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1262 }
1263
1264 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1265 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1266 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1267
1268 if metadata.is_none() {
1269 collection_details.item_metadatas.saturating_inc();
1270 }
1271 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1272 collection_details.total_deposit.saturating_reduce(old_deposit);
1273 let mut deposit = Zero::zero();
1274 if !collection_details.free_holding && maybe_check_owner.is_some() {
1275 deposit = T::DepositPerByte::get()
1276 .saturating_mul(((data.len()) as u32).into())
1277 .saturating_add(T::MetadataDepositBase::get());
1278 }
1279 if deposit > old_deposit {
1280 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1281 } else if deposit < old_deposit {
1282 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1283 }
1284 collection_details.total_deposit.saturating_accrue(deposit);
1285
1286 *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen });
1287
1288 Collection::<T, I>::insert(&collection, &collection_details);
1289 Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen });
1290 Ok(())
1291 })
1292 }
1293
1294 #[pallet::call_index(19)]
1308 #[pallet::weight(T::WeightInfo::clear_metadata())]
1309 pub fn clear_metadata(
1310 origin: OriginFor<T>,
1311 collection: T::CollectionId,
1312 item: T::ItemId,
1313 ) -> DispatchResult {
1314 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1315 .map(|_| None)
1316 .or_else(|origin| ensure_signed(origin).map(Some))?;
1317
1318 let mut collection_details =
1319 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1320 if let Some(check_owner) = &maybe_check_owner {
1321 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1322 }
1323
1324 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1325 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1326 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1327
1328 if metadata.is_some() {
1329 collection_details.item_metadatas.saturating_dec();
1330 }
1331 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1332 T::Currency::unreserve(&collection_details.owner, deposit);
1333 collection_details.total_deposit.saturating_reduce(deposit);
1334
1335 Collection::<T, I>::insert(&collection, &collection_details);
1336 Self::deposit_event(Event::MetadataCleared { collection, item });
1337 Ok(())
1338 })
1339 }
1340
1341 #[pallet::call_index(20)]
1358 #[pallet::weight(T::WeightInfo::set_collection_metadata())]
1359 pub fn set_collection_metadata(
1360 origin: OriginFor<T>,
1361 collection: T::CollectionId,
1362 data: BoundedVec<u8, T::StringLimit>,
1363 is_frozen: bool,
1364 ) -> DispatchResult {
1365 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1366 .map(|_| None)
1367 .or_else(|origin| ensure_signed(origin).map(Some))?;
1368
1369 let mut details =
1370 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1371 if let Some(check_owner) = &maybe_check_owner {
1372 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1373 }
1374
1375 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1376 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1377 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1378
1379 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1380 details.total_deposit.saturating_reduce(old_deposit);
1381 let mut deposit = Zero::zero();
1382 if maybe_check_owner.is_some() && !details.free_holding {
1383 deposit = T::DepositPerByte::get()
1384 .saturating_mul(((data.len()) as u32).into())
1385 .saturating_add(T::MetadataDepositBase::get());
1386 }
1387 if deposit > old_deposit {
1388 T::Currency::reserve(&details.owner, deposit - old_deposit)?;
1389 } else if deposit < old_deposit {
1390 T::Currency::unreserve(&details.owner, old_deposit - deposit);
1391 }
1392 details.total_deposit.saturating_accrue(deposit);
1393
1394 Collection::<T, I>::insert(&collection, details);
1395
1396 *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen });
1397
1398 Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen });
1399 Ok(())
1400 })
1401 }
1402
1403 #[pallet::call_index(21)]
1416 #[pallet::weight(T::WeightInfo::clear_collection_metadata())]
1417 pub fn clear_collection_metadata(
1418 origin: OriginFor<T>,
1419 collection: T::CollectionId,
1420 ) -> DispatchResult {
1421 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1422 .map(|_| None)
1423 .or_else(|origin| ensure_signed(origin).map(Some))?;
1424
1425 let mut details =
1426 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1427 if let Some(check_owner) = &maybe_check_owner {
1428 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1429 }
1430
1431 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1432 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1433 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1434
1435 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1436 T::Currency::unreserve(&details.owner, deposit);
1437 details.total_deposit.saturating_reduce(deposit);
1438 Collection::<T, I>::insert(&collection, details);
1439 Self::deposit_event(Event::CollectionMetadataCleared { collection });
1440 Ok(())
1441 })
1442 }
1443
1444 #[pallet::call_index(22)]
1455 #[pallet::weight(T::WeightInfo::set_accept_ownership())]
1456 pub fn set_accept_ownership(
1457 origin: OriginFor<T>,
1458 maybe_collection: Option<T::CollectionId>,
1459 ) -> DispatchResult {
1460 let who = ensure_signed(origin)?;
1461 let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
1462 match (exists, maybe_collection.is_some()) {
1463 (false, true) => {
1464 frame_system::Pallet::<T>::inc_consumers(&who)?;
1465 },
1466 (true, false) => {
1467 frame_system::Pallet::<T>::dec_consumers(&who);
1468 },
1469 _ => {},
1470 }
1471 if let Some(collection) = maybe_collection.as_ref() {
1472 OwnershipAcceptance::<T, I>::insert(&who, collection);
1473 } else {
1474 OwnershipAcceptance::<T, I>::remove(&who);
1475 }
1476 Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
1477 Ok(())
1478 }
1479
1480 #[pallet::call_index(23)]
1492 #[pallet::weight(T::WeightInfo::set_collection_max_supply())]
1493 pub fn set_collection_max_supply(
1494 origin: OriginFor<T>,
1495 collection: T::CollectionId,
1496 max_supply: u32,
1497 ) -> DispatchResult {
1498 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1499 .map(|_| None)
1500 .or_else(|origin| ensure_signed(origin).map(Some))?;
1501
1502 ensure!(
1503 !CollectionMaxSupply::<T, I>::contains_key(&collection),
1504 Error::<T, I>::MaxSupplyAlreadySet
1505 );
1506
1507 let details =
1508 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1509 if let Some(check_owner) = &maybe_check_owner {
1510 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1511 }
1512
1513 ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
1514
1515 CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
1516 Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
1517 Ok(())
1518 }
1519
1520 #[pallet::call_index(24)]
1532 #[pallet::weight(T::WeightInfo::set_price())]
1533 pub fn set_price(
1534 origin: OriginFor<T>,
1535 collection: T::CollectionId,
1536 item: T::ItemId,
1537 price: Option<ItemPrice<T, I>>,
1538 whitelisted_buyer: Option<AccountIdLookupOf<T>>,
1539 ) -> DispatchResult {
1540 let origin = ensure_signed(origin)?;
1541 let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1542 Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1543 }
1544
1545 #[pallet::call_index(25)]
1555 #[pallet::weight(T::WeightInfo::buy_item())]
1556 pub fn buy_item(
1557 origin: OriginFor<T>,
1558 collection: T::CollectionId,
1559 item: T::ItemId,
1560 bid_price: ItemPrice<T, I>,
1561 ) -> DispatchResult {
1562 let origin = ensure_signed(origin)?;
1563 Self::do_buy_item(collection, item, origin, bid_price)
1564 }
1565 }
1566}