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, RuntimeDebug,
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 }
436
437 impl<T: Config<I>, I: 'static> Pallet<T, I> {
438 pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
440 Item::<T, I>::get(collection, item).map(|i| i.owner)
441 }
442
443 pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
445 Collection::<T, I>::get(collection).map(|i| i.owner)
446 }
447 }
448
449 #[pallet::call]
450 impl<T: Config<I>, I: 'static> Pallet<T, I> {
451 #[pallet::call_index(0)]
468 #[pallet::weight(T::WeightInfo::create())]
469 pub fn create(
470 origin: OriginFor<T>,
471 collection: T::CollectionId,
472 admin: AccountIdLookupOf<T>,
473 ) -> DispatchResult {
474 let owner = T::CreateOrigin::ensure_origin(origin, &collection)?;
475 let admin = T::Lookup::lookup(admin)?;
476
477 Self::do_create_collection(
478 collection.clone(),
479 owner.clone(),
480 admin.clone(),
481 T::CollectionDeposit::get(),
482 false,
483 Event::Created { collection, creator: owner, owner: admin },
484 )
485 }
486
487 #[pallet::call_index(1)]
505 #[pallet::weight(T::WeightInfo::force_create())]
506 pub fn force_create(
507 origin: OriginFor<T>,
508 collection: T::CollectionId,
509 owner: AccountIdLookupOf<T>,
510 free_holding: bool,
511 ) -> DispatchResult {
512 T::ForceOrigin::ensure_origin(origin)?;
513 let owner = T::Lookup::lookup(owner)?;
514
515 Self::do_create_collection(
516 collection.clone(),
517 owner.clone(),
518 owner.clone(),
519 Zero::zero(),
520 free_holding,
521 Event::ForceCreated { collection, owner },
522 )
523 }
524
525 #[pallet::call_index(2)]
541 #[pallet::weight(T::WeightInfo::destroy(
542 witness.items,
543 witness.item_metadatas,
544 witness.attributes,
545 ))]
546 pub fn destroy(
547 origin: OriginFor<T>,
548 collection: T::CollectionId,
549 witness: DestroyWitness,
550 ) -> DispatchResultWithPostInfo {
551 let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
552 Ok(_) => None,
553 Err(origin) => Some(ensure_signed(origin)?),
554 };
555 let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
556
557 Ok(Some(T::WeightInfo::destroy(
558 details.items,
559 details.item_metadatas,
560 details.attributes,
561 ))
562 .into())
563 }
564
565 #[pallet::call_index(3)]
577 #[pallet::weight(T::WeightInfo::mint())]
578 pub fn mint(
579 origin: OriginFor<T>,
580 collection: T::CollectionId,
581 item: T::ItemId,
582 owner: AccountIdLookupOf<T>,
583 ) -> DispatchResult {
584 let origin = ensure_signed(origin)?;
585 let owner = T::Lookup::lookup(owner)?;
586
587 Self::do_mint(collection, item, owner, |collection_details| {
588 ensure!(collection_details.issuer == origin, Error::<T, I>::NoPermission);
589 Ok(())
590 })
591 }
592
593 #[pallet::call_index(4)]
609 #[pallet::weight(T::WeightInfo::burn())]
610 pub fn burn(
611 origin: OriginFor<T>,
612 collection: T::CollectionId,
613 item: T::ItemId,
614 check_owner: Option<AccountIdLookupOf<T>>,
615 ) -> DispatchResult {
616 let origin = ensure_signed(origin)?;
617 let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
618
619 Self::do_burn(collection, item, |collection_details, details| {
620 let is_permitted = collection_details.admin == origin || details.owner == origin;
621 ensure!(is_permitted, Error::<T, I>::NoPermission);
622 ensure!(
623 check_owner.map_or(true, |o| o == details.owner),
624 Error::<T, I>::WrongOwner
625 );
626 Ok(())
627 })
628 }
629
630 #[pallet::call_index(5)]
648 #[pallet::weight(T::WeightInfo::transfer())]
649 pub fn transfer(
650 origin: OriginFor<T>,
651 collection: T::CollectionId,
652 item: T::ItemId,
653 dest: AccountIdLookupOf<T>,
654 ) -> DispatchResult {
655 let origin = ensure_signed(origin)?;
656 let dest = T::Lookup::lookup(dest)?;
657
658 Self::do_transfer(collection, item, dest, |collection_details, details| {
659 if details.owner != origin && collection_details.admin != origin {
660 let approved = details.approved.take().map_or(false, |i| i == origin);
661 ensure!(approved, Error::<T, I>::NoPermission);
662 }
663 Ok(())
664 })
665 }
666
667 #[pallet::call_index(6)]
685 #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))]
686 pub fn redeposit(
687 origin: OriginFor<T>,
688 collection: T::CollectionId,
689 items: Vec<T::ItemId>,
690 ) -> DispatchResult {
691 let origin = ensure_signed(origin)?;
692
693 let mut collection_details =
694 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
695 ensure!(collection_details.owner == origin, Error::<T, I>::NoPermission);
696 let deposit = match collection_details.free_holding {
697 true => Zero::zero(),
698 false => T::ItemDeposit::get(),
699 };
700
701 let mut successful = Vec::with_capacity(items.len());
702 for item in items.into_iter() {
703 let mut details = match Item::<T, I>::get(&collection, &item) {
704 Some(x) => x,
705 None => continue,
706 };
707 let old = details.deposit;
708 if old > deposit {
709 T::Currency::unreserve(&collection_details.owner, old - deposit);
710 } else if deposit > old {
711 if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() {
712 continue
715 }
716 } else {
717 continue
718 }
719 collection_details.total_deposit.saturating_accrue(deposit);
720 collection_details.total_deposit.saturating_reduce(old);
721 details.deposit = deposit;
722 Item::<T, I>::insert(&collection, &item, &details);
723 successful.push(item);
724 }
725 Collection::<T, I>::insert(&collection, &collection_details);
726
727 Self::deposit_event(Event::<T, I>::Redeposited {
728 collection,
729 successful_items: successful,
730 });
731
732 Ok(())
733 }
734
735 #[pallet::call_index(7)]
746 #[pallet::weight(T::WeightInfo::freeze())]
747 pub fn freeze(
748 origin: OriginFor<T>,
749 collection: T::CollectionId,
750 item: T::ItemId,
751 ) -> DispatchResult {
752 let origin = ensure_signed(origin)?;
753
754 let mut details =
755 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
756 let collection_details =
757 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
758 ensure!(collection_details.freezer == origin, Error::<T, I>::NoPermission);
759
760 details.is_frozen = true;
761 Item::<T, I>::insert(&collection, &item, &details);
762
763 Self::deposit_event(Event::<T, I>::Frozen { collection, item });
764 Ok(())
765 }
766
767 #[pallet::call_index(8)]
778 #[pallet::weight(T::WeightInfo::thaw())]
779 pub fn thaw(
780 origin: OriginFor<T>,
781 collection: T::CollectionId,
782 item: T::ItemId,
783 ) -> DispatchResult {
784 let origin = ensure_signed(origin)?;
785
786 let mut details =
787 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
788 let collection_details =
789 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
790 ensure!(collection_details.admin == origin, Error::<T, I>::NoPermission);
791
792 details.is_frozen = false;
793 Item::<T, I>::insert(&collection, &item, &details);
794
795 Self::deposit_event(Event::<T, I>::Thawed { collection, item });
796 Ok(())
797 }
798
799 #[pallet::call_index(9)]
809 #[pallet::weight(T::WeightInfo::freeze_collection())]
810 pub fn freeze_collection(
811 origin: OriginFor<T>,
812 collection: T::CollectionId,
813 ) -> DispatchResult {
814 let origin = ensure_signed(origin)?;
815
816 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
817 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
818 ensure!(origin == details.freezer, Error::<T, I>::NoPermission);
819
820 details.is_frozen = true;
821
822 Self::deposit_event(Event::<T, I>::CollectionFrozen { collection });
823 Ok(())
824 })
825 }
826
827 #[pallet::call_index(10)]
837 #[pallet::weight(T::WeightInfo::thaw_collection())]
838 pub fn thaw_collection(
839 origin: OriginFor<T>,
840 collection: T::CollectionId,
841 ) -> DispatchResult {
842 let origin = ensure_signed(origin)?;
843
844 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
845 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
846 ensure!(origin == details.admin, Error::<T, I>::NoPermission);
847
848 details.is_frozen = false;
849
850 Self::deposit_event(Event::<T, I>::CollectionThawed { collection });
851 Ok(())
852 })
853 }
854
855 #[pallet::call_index(11)]
867 #[pallet::weight(T::WeightInfo::transfer_ownership())]
868 pub fn transfer_ownership(
869 origin: OriginFor<T>,
870 collection: T::CollectionId,
871 new_owner: AccountIdLookupOf<T>,
872 ) -> DispatchResult {
873 let origin = ensure_signed(origin)?;
874 let new_owner = T::Lookup::lookup(new_owner)?;
875
876 let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
877 ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
878
879 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
880 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
881 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
882 if details.owner == new_owner {
883 return Ok(())
884 }
885
886 T::Currency::repatriate_reserved(
888 &details.owner,
889 &new_owner,
890 details.total_deposit,
891 Reserved,
892 )?;
893
894 CollectionAccount::<T, I>::remove(&details.owner, &collection);
895 CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
896
897 details.owner = new_owner.clone();
898 OwnershipAcceptance::<T, I>::remove(&new_owner);
899 frame_system::Pallet::<T>::dec_consumers(&new_owner);
900
901 Self::deposit_event(Event::OwnerChanged { collection, new_owner });
902 Ok(())
903 })
904 }
905
906 #[pallet::call_index(12)]
919 #[pallet::weight(T::WeightInfo::set_team())]
920 pub fn set_team(
921 origin: OriginFor<T>,
922 collection: T::CollectionId,
923 issuer: AccountIdLookupOf<T>,
924 admin: AccountIdLookupOf<T>,
925 freezer: AccountIdLookupOf<T>,
926 ) -> DispatchResult {
927 let origin = ensure_signed(origin)?;
928 let issuer = T::Lookup::lookup(issuer)?;
929 let admin = T::Lookup::lookup(admin)?;
930 let freezer = T::Lookup::lookup(freezer)?;
931
932 Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
933 let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
934 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
935
936 details.issuer = issuer.clone();
937 details.admin = admin.clone();
938 details.freezer = freezer.clone();
939
940 Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
941 Ok(())
942 })
943 }
944
945 #[pallet::call_index(13)]
960 #[pallet::weight(T::WeightInfo::approve_transfer())]
961 pub fn approve_transfer(
962 origin: OriginFor<T>,
963 collection: T::CollectionId,
964 item: T::ItemId,
965 delegate: AccountIdLookupOf<T>,
966 ) -> DispatchResult {
967 let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
968 .map(|_| None)
969 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
970
971 let delegate = T::Lookup::lookup(delegate)?;
972
973 let collection_details =
974 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
975 let mut details =
976 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
977
978 if let Some(check) = maybe_check {
979 let permitted = check == collection_details.admin || check == details.owner;
980 ensure!(permitted, Error::<T, I>::NoPermission);
981 }
982
983 details.approved = Some(delegate);
984 Item::<T, I>::insert(&collection, &item, &details);
985
986 let delegate = details.approved.expect("set as Some above; qed");
987 Self::deposit_event(Event::ApprovedTransfer {
988 collection,
989 item,
990 owner: details.owner,
991 delegate,
992 });
993
994 Ok(())
995 }
996
997 #[pallet::call_index(14)]
1014 #[pallet::weight(T::WeightInfo::cancel_approval())]
1015 pub fn cancel_approval(
1016 origin: OriginFor<T>,
1017 collection: T::CollectionId,
1018 item: T::ItemId,
1019 maybe_check_delegate: Option<AccountIdLookupOf<T>>,
1020 ) -> DispatchResult {
1021 let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
1022 .map(|_| None)
1023 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1024
1025 let collection_details =
1026 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1027 let mut details =
1028 Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
1029 if let Some(check) = maybe_check {
1030 let permitted = check == collection_details.admin || check == details.owner;
1031 ensure!(permitted, Error::<T, I>::NoPermission);
1032 }
1033 let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?;
1034 let old = details.approved.take().ok_or(Error::<T, I>::NoDelegate)?;
1035 if let Some(check_delegate) = maybe_check_delegate {
1036 ensure!(check_delegate == old, Error::<T, I>::WrongDelegate);
1037 }
1038
1039 Item::<T, I>::insert(&collection, &item, &details);
1040 Self::deposit_event(Event::ApprovalCancelled {
1041 collection,
1042 item,
1043 owner: details.owner,
1044 delegate: old,
1045 });
1046
1047 Ok(())
1048 }
1049
1050 #[pallet::call_index(15)]
1067 #[pallet::weight(T::WeightInfo::force_item_status())]
1068 pub fn force_item_status(
1069 origin: OriginFor<T>,
1070 collection: T::CollectionId,
1071 owner: AccountIdLookupOf<T>,
1072 issuer: AccountIdLookupOf<T>,
1073 admin: AccountIdLookupOf<T>,
1074 freezer: AccountIdLookupOf<T>,
1075 free_holding: bool,
1076 is_frozen: bool,
1077 ) -> DispatchResult {
1078 T::ForceOrigin::ensure_origin(origin)?;
1079
1080 Collection::<T, I>::try_mutate(collection.clone(), |maybe_item| {
1081 let mut item = maybe_item.take().ok_or(Error::<T, I>::UnknownCollection)?;
1082 let old_owner = item.owner;
1083 let new_owner = T::Lookup::lookup(owner)?;
1084 item.owner = new_owner.clone();
1085 item.issuer = T::Lookup::lookup(issuer)?;
1086 item.admin = T::Lookup::lookup(admin)?;
1087 item.freezer = T::Lookup::lookup(freezer)?;
1088 item.free_holding = free_holding;
1089 item.is_frozen = is_frozen;
1090 *maybe_item = Some(item);
1091 CollectionAccount::<T, I>::remove(&old_owner, &collection);
1092 CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
1093
1094 Self::deposit_event(Event::ItemStatusChanged { collection });
1095 Ok(())
1096 })
1097 }
1098
1099 #[pallet::call_index(16)]
1117 #[pallet::weight(T::WeightInfo::set_attribute())]
1118 pub fn set_attribute(
1119 origin: OriginFor<T>,
1120 collection: T::CollectionId,
1121 maybe_item: Option<T::ItemId>,
1122 key: BoundedVec<u8, T::KeyLimit>,
1123 value: BoundedVec<u8, T::ValueLimit>,
1124 ) -> DispatchResult {
1125 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1126 .map(|_| None)
1127 .or_else(|origin| ensure_signed(origin).map(Some))?;
1128
1129 let mut collection_details =
1130 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1131 if let Some(check_owner) = &maybe_check_owner {
1132 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1133 }
1134 let maybe_is_frozen = match maybe_item {
1135 None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1136 Some(item) =>
1137 ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen),
1138 };
1139 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1140
1141 let attribute = Attribute::<T, I>::get((collection.clone(), maybe_item, &key));
1142 if attribute.is_none() {
1143 collection_details.attributes.saturating_inc();
1144 }
1145 let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
1146 collection_details.total_deposit.saturating_reduce(old_deposit);
1147 let mut deposit = Zero::zero();
1148 if !collection_details.free_holding && maybe_check_owner.is_some() {
1149 deposit = T::DepositPerByte::get()
1150 .saturating_mul(((key.len() + value.len()) as u32).into())
1151 .saturating_add(T::AttributeDepositBase::get());
1152 }
1153 collection_details.total_deposit.saturating_accrue(deposit);
1154 if deposit > old_deposit {
1155 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1156 } else if deposit < old_deposit {
1157 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1158 }
1159
1160 Attribute::<T, I>::insert((&collection, maybe_item, &key), (&value, deposit));
1161 Collection::<T, I>::insert(collection.clone(), &collection_details);
1162 Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value });
1163 Ok(())
1164 }
1165
1166 #[pallet::call_index(17)]
1181 #[pallet::weight(T::WeightInfo::clear_attribute())]
1182 pub fn clear_attribute(
1183 origin: OriginFor<T>,
1184 collection: T::CollectionId,
1185 maybe_item: Option<T::ItemId>,
1186 key: BoundedVec<u8, T::KeyLimit>,
1187 ) -> DispatchResult {
1188 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1189 .map(|_| None)
1190 .or_else(|origin| ensure_signed(origin).map(Some))?;
1191
1192 let mut collection_details =
1193 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1194 if let Some(check_owner) = &maybe_check_owner {
1195 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1196 }
1197 let maybe_is_frozen = match maybe_item {
1198 None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1199 Some(item) =>
1200 ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen),
1201 };
1202 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1203
1204 if let Some((_, deposit)) =
1205 Attribute::<T, I>::take((collection.clone(), maybe_item, &key))
1206 {
1207 collection_details.attributes.saturating_dec();
1208 collection_details.total_deposit.saturating_reduce(deposit);
1209 T::Currency::unreserve(&collection_details.owner, deposit);
1210 Collection::<T, I>::insert(collection.clone(), &collection_details);
1211 Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key });
1212 }
1213 Ok(())
1214 }
1215
1216 #[pallet::call_index(18)]
1234 #[pallet::weight(T::WeightInfo::set_metadata())]
1235 pub fn set_metadata(
1236 origin: OriginFor<T>,
1237 collection: T::CollectionId,
1238 item: T::ItemId,
1239 data: BoundedVec<u8, T::StringLimit>,
1240 is_frozen: bool,
1241 ) -> DispatchResult {
1242 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1243 .map(|_| None)
1244 .or_else(|origin| ensure_signed(origin).map(Some))?;
1245
1246 let mut collection_details =
1247 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1248
1249 if let Some(check_owner) = &maybe_check_owner {
1250 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1251 }
1252
1253 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1254 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1255 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1256
1257 if metadata.is_none() {
1258 collection_details.item_metadatas.saturating_inc();
1259 }
1260 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1261 collection_details.total_deposit.saturating_reduce(old_deposit);
1262 let mut deposit = Zero::zero();
1263 if !collection_details.free_holding && maybe_check_owner.is_some() {
1264 deposit = T::DepositPerByte::get()
1265 .saturating_mul(((data.len()) as u32).into())
1266 .saturating_add(T::MetadataDepositBase::get());
1267 }
1268 if deposit > old_deposit {
1269 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1270 } else if deposit < old_deposit {
1271 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1272 }
1273 collection_details.total_deposit.saturating_accrue(deposit);
1274
1275 *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen });
1276
1277 Collection::<T, I>::insert(&collection, &collection_details);
1278 Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen });
1279 Ok(())
1280 })
1281 }
1282
1283 #[pallet::call_index(19)]
1297 #[pallet::weight(T::WeightInfo::clear_metadata())]
1298 pub fn clear_metadata(
1299 origin: OriginFor<T>,
1300 collection: T::CollectionId,
1301 item: T::ItemId,
1302 ) -> DispatchResult {
1303 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1304 .map(|_| None)
1305 .or_else(|origin| ensure_signed(origin).map(Some))?;
1306
1307 let mut collection_details =
1308 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1309 if let Some(check_owner) = &maybe_check_owner {
1310 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1311 }
1312
1313 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1314 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1315 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1316
1317 if metadata.is_some() {
1318 collection_details.item_metadatas.saturating_dec();
1319 }
1320 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1321 T::Currency::unreserve(&collection_details.owner, deposit);
1322 collection_details.total_deposit.saturating_reduce(deposit);
1323
1324 Collection::<T, I>::insert(&collection, &collection_details);
1325 Self::deposit_event(Event::MetadataCleared { collection, item });
1326 Ok(())
1327 })
1328 }
1329
1330 #[pallet::call_index(20)]
1347 #[pallet::weight(T::WeightInfo::set_collection_metadata())]
1348 pub fn set_collection_metadata(
1349 origin: OriginFor<T>,
1350 collection: T::CollectionId,
1351 data: BoundedVec<u8, T::StringLimit>,
1352 is_frozen: bool,
1353 ) -> DispatchResult {
1354 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1355 .map(|_| None)
1356 .or_else(|origin| ensure_signed(origin).map(Some))?;
1357
1358 let mut details =
1359 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1360 if let Some(check_owner) = &maybe_check_owner {
1361 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1362 }
1363
1364 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1365 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1366 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1367
1368 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1369 details.total_deposit.saturating_reduce(old_deposit);
1370 let mut deposit = Zero::zero();
1371 if maybe_check_owner.is_some() && !details.free_holding {
1372 deposit = T::DepositPerByte::get()
1373 .saturating_mul(((data.len()) as u32).into())
1374 .saturating_add(T::MetadataDepositBase::get());
1375 }
1376 if deposit > old_deposit {
1377 T::Currency::reserve(&details.owner, deposit - old_deposit)?;
1378 } else if deposit < old_deposit {
1379 T::Currency::unreserve(&details.owner, old_deposit - deposit);
1380 }
1381 details.total_deposit.saturating_accrue(deposit);
1382
1383 Collection::<T, I>::insert(&collection, details);
1384
1385 *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen });
1386
1387 Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen });
1388 Ok(())
1389 })
1390 }
1391
1392 #[pallet::call_index(21)]
1405 #[pallet::weight(T::WeightInfo::clear_collection_metadata())]
1406 pub fn clear_collection_metadata(
1407 origin: OriginFor<T>,
1408 collection: T::CollectionId,
1409 ) -> DispatchResult {
1410 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1411 .map(|_| None)
1412 .or_else(|origin| ensure_signed(origin).map(Some))?;
1413
1414 let mut details =
1415 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1416 if let Some(check_owner) = &maybe_check_owner {
1417 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1418 }
1419
1420 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1421 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1422 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1423
1424 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1425 T::Currency::unreserve(&details.owner, deposit);
1426 details.total_deposit.saturating_reduce(deposit);
1427 Collection::<T, I>::insert(&collection, details);
1428 Self::deposit_event(Event::CollectionMetadataCleared { collection });
1429 Ok(())
1430 })
1431 }
1432
1433 #[pallet::call_index(22)]
1444 #[pallet::weight(T::WeightInfo::set_accept_ownership())]
1445 pub fn set_accept_ownership(
1446 origin: OriginFor<T>,
1447 maybe_collection: Option<T::CollectionId>,
1448 ) -> DispatchResult {
1449 let who = ensure_signed(origin)?;
1450 let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
1451 match (exists, maybe_collection.is_some()) {
1452 (false, true) => {
1453 frame_system::Pallet::<T>::inc_consumers(&who)?;
1454 },
1455 (true, false) => {
1456 frame_system::Pallet::<T>::dec_consumers(&who);
1457 },
1458 _ => {},
1459 }
1460 if let Some(collection) = maybe_collection.as_ref() {
1461 OwnershipAcceptance::<T, I>::insert(&who, collection);
1462 } else {
1463 OwnershipAcceptance::<T, I>::remove(&who);
1464 }
1465 Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
1466 Ok(())
1467 }
1468
1469 #[pallet::call_index(23)]
1481 #[pallet::weight(T::WeightInfo::set_collection_max_supply())]
1482 pub fn set_collection_max_supply(
1483 origin: OriginFor<T>,
1484 collection: T::CollectionId,
1485 max_supply: u32,
1486 ) -> DispatchResult {
1487 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1488 .map(|_| None)
1489 .or_else(|origin| ensure_signed(origin).map(Some))?;
1490
1491 ensure!(
1492 !CollectionMaxSupply::<T, I>::contains_key(&collection),
1493 Error::<T, I>::MaxSupplyAlreadySet
1494 );
1495
1496 let details =
1497 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1498 if let Some(check_owner) = &maybe_check_owner {
1499 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1500 }
1501
1502 ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
1503
1504 CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
1505 Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
1506 Ok(())
1507 }
1508
1509 #[pallet::call_index(24)]
1521 #[pallet::weight(T::WeightInfo::set_price())]
1522 pub fn set_price(
1523 origin: OriginFor<T>,
1524 collection: T::CollectionId,
1525 item: T::ItemId,
1526 price: Option<ItemPrice<T, I>>,
1527 whitelisted_buyer: Option<AccountIdLookupOf<T>>,
1528 ) -> DispatchResult {
1529 let origin = ensure_signed(origin)?;
1530 let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1531 Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1532 }
1533
1534 #[pallet::call_index(25)]
1544 #[pallet::weight(T::WeightInfo::buy_item())]
1545 pub fn buy_item(
1546 origin: OriginFor<T>,
1547 collection: T::CollectionId,
1548 item: T::ItemId,
1549 bid_price: ItemPrice<T, I>,
1550 ) -> DispatchResult {
1551 let origin = ensure_signed(origin)?;
1552 Self::do_buy_item(collection, item, origin, bid_price)
1553 }
1554 }
1555}