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 }
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 };
1140 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1141
1142 let attribute = Attribute::<T, I>::get((collection.clone(), maybe_item, &key));
1143 if attribute.is_none() {
1144 collection_details.attributes.saturating_inc();
1145 }
1146 let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
1147 collection_details.total_deposit.saturating_reduce(old_deposit);
1148 let mut deposit = Zero::zero();
1149 if !collection_details.free_holding && maybe_check_owner.is_some() {
1150 deposit = T::DepositPerByte::get()
1151 .saturating_mul(((key.len() + value.len()) as u32).into())
1152 .saturating_add(T::AttributeDepositBase::get());
1153 }
1154 collection_details.total_deposit.saturating_accrue(deposit);
1155 if deposit > old_deposit {
1156 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1157 } else if deposit < old_deposit {
1158 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1159 }
1160
1161 Attribute::<T, I>::insert((&collection, maybe_item, &key), (&value, deposit));
1162 Collection::<T, I>::insert(collection.clone(), &collection_details);
1163 Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value });
1164 Ok(())
1165 }
1166
1167 #[pallet::call_index(17)]
1182 #[pallet::weight(T::WeightInfo::clear_attribute())]
1183 pub fn clear_attribute(
1184 origin: OriginFor<T>,
1185 collection: T::CollectionId,
1186 maybe_item: Option<T::ItemId>,
1187 key: BoundedVec<u8, T::KeyLimit>,
1188 ) -> DispatchResult {
1189 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1190 .map(|_| None)
1191 .or_else(|origin| ensure_signed(origin).map(Some))?;
1192
1193 let mut collection_details =
1194 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1195 if let Some(check_owner) = &maybe_check_owner {
1196 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1197 }
1198 let maybe_is_frozen = match maybe_item {
1199 None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
1200 Some(item) => {
1201 ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen)
1202 },
1203 };
1204 ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
1205
1206 if let Some((_, deposit)) =
1207 Attribute::<T, I>::take((collection.clone(), maybe_item, &key))
1208 {
1209 collection_details.attributes.saturating_dec();
1210 collection_details.total_deposit.saturating_reduce(deposit);
1211 T::Currency::unreserve(&collection_details.owner, deposit);
1212 Collection::<T, I>::insert(collection.clone(), &collection_details);
1213 Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key });
1214 }
1215 Ok(())
1216 }
1217
1218 #[pallet::call_index(18)]
1236 #[pallet::weight(T::WeightInfo::set_metadata())]
1237 pub fn set_metadata(
1238 origin: OriginFor<T>,
1239 collection: T::CollectionId,
1240 item: T::ItemId,
1241 data: BoundedVec<u8, T::StringLimit>,
1242 is_frozen: bool,
1243 ) -> DispatchResult {
1244 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1245 .map(|_| None)
1246 .or_else(|origin| ensure_signed(origin).map(Some))?;
1247
1248 let mut collection_details =
1249 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1250
1251 if let Some(check_owner) = &maybe_check_owner {
1252 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1253 }
1254
1255 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1256 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1257 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1258
1259 if metadata.is_none() {
1260 collection_details.item_metadatas.saturating_inc();
1261 }
1262 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1263 collection_details.total_deposit.saturating_reduce(old_deposit);
1264 let mut deposit = Zero::zero();
1265 if !collection_details.free_holding && maybe_check_owner.is_some() {
1266 deposit = T::DepositPerByte::get()
1267 .saturating_mul(((data.len()) as u32).into())
1268 .saturating_add(T::MetadataDepositBase::get());
1269 }
1270 if deposit > old_deposit {
1271 T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
1272 } else if deposit < old_deposit {
1273 T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
1274 }
1275 collection_details.total_deposit.saturating_accrue(deposit);
1276
1277 *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen });
1278
1279 Collection::<T, I>::insert(&collection, &collection_details);
1280 Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen });
1281 Ok(())
1282 })
1283 }
1284
1285 #[pallet::call_index(19)]
1299 #[pallet::weight(T::WeightInfo::clear_metadata())]
1300 pub fn clear_metadata(
1301 origin: OriginFor<T>,
1302 collection: T::CollectionId,
1303 item: T::ItemId,
1304 ) -> DispatchResult {
1305 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1306 .map(|_| None)
1307 .or_else(|origin| ensure_signed(origin).map(Some))?;
1308
1309 let mut collection_details =
1310 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1311 if let Some(check_owner) = &maybe_check_owner {
1312 ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
1313 }
1314
1315 ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
1316 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1317 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1318
1319 if metadata.is_some() {
1320 collection_details.item_metadatas.saturating_dec();
1321 }
1322 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1323 T::Currency::unreserve(&collection_details.owner, deposit);
1324 collection_details.total_deposit.saturating_reduce(deposit);
1325
1326 Collection::<T, I>::insert(&collection, &collection_details);
1327 Self::deposit_event(Event::MetadataCleared { collection, item });
1328 Ok(())
1329 })
1330 }
1331
1332 #[pallet::call_index(20)]
1349 #[pallet::weight(T::WeightInfo::set_collection_metadata())]
1350 pub fn set_collection_metadata(
1351 origin: OriginFor<T>,
1352 collection: T::CollectionId,
1353 data: BoundedVec<u8, T::StringLimit>,
1354 is_frozen: bool,
1355 ) -> DispatchResult {
1356 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1357 .map(|_| None)
1358 .or_else(|origin| ensure_signed(origin).map(Some))?;
1359
1360 let mut details =
1361 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1362 if let Some(check_owner) = &maybe_check_owner {
1363 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1364 }
1365
1366 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1367 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1368 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1369
1370 let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1371 details.total_deposit.saturating_reduce(old_deposit);
1372 let mut deposit = Zero::zero();
1373 if maybe_check_owner.is_some() && !details.free_holding {
1374 deposit = T::DepositPerByte::get()
1375 .saturating_mul(((data.len()) as u32).into())
1376 .saturating_add(T::MetadataDepositBase::get());
1377 }
1378 if deposit > old_deposit {
1379 T::Currency::reserve(&details.owner, deposit - old_deposit)?;
1380 } else if deposit < old_deposit {
1381 T::Currency::unreserve(&details.owner, old_deposit - deposit);
1382 }
1383 details.total_deposit.saturating_accrue(deposit);
1384
1385 Collection::<T, I>::insert(&collection, details);
1386
1387 *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen });
1388
1389 Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen });
1390 Ok(())
1391 })
1392 }
1393
1394 #[pallet::call_index(21)]
1407 #[pallet::weight(T::WeightInfo::clear_collection_metadata())]
1408 pub fn clear_collection_metadata(
1409 origin: OriginFor<T>,
1410 collection: T::CollectionId,
1411 ) -> DispatchResult {
1412 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1413 .map(|_| None)
1414 .or_else(|origin| ensure_signed(origin).map(Some))?;
1415
1416 let mut details =
1417 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1418 if let Some(check_owner) = &maybe_check_owner {
1419 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1420 }
1421
1422 CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
1423 let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
1424 ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
1425
1426 let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
1427 T::Currency::unreserve(&details.owner, deposit);
1428 details.total_deposit.saturating_reduce(deposit);
1429 Collection::<T, I>::insert(&collection, details);
1430 Self::deposit_event(Event::CollectionMetadataCleared { collection });
1431 Ok(())
1432 })
1433 }
1434
1435 #[pallet::call_index(22)]
1446 #[pallet::weight(T::WeightInfo::set_accept_ownership())]
1447 pub fn set_accept_ownership(
1448 origin: OriginFor<T>,
1449 maybe_collection: Option<T::CollectionId>,
1450 ) -> DispatchResult {
1451 let who = ensure_signed(origin)?;
1452 let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
1453 match (exists, maybe_collection.is_some()) {
1454 (false, true) => {
1455 frame_system::Pallet::<T>::inc_consumers(&who)?;
1456 },
1457 (true, false) => {
1458 frame_system::Pallet::<T>::dec_consumers(&who);
1459 },
1460 _ => {},
1461 }
1462 if let Some(collection) = maybe_collection.as_ref() {
1463 OwnershipAcceptance::<T, I>::insert(&who, collection);
1464 } else {
1465 OwnershipAcceptance::<T, I>::remove(&who);
1466 }
1467 Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
1468 Ok(())
1469 }
1470
1471 #[pallet::call_index(23)]
1483 #[pallet::weight(T::WeightInfo::set_collection_max_supply())]
1484 pub fn set_collection_max_supply(
1485 origin: OriginFor<T>,
1486 collection: T::CollectionId,
1487 max_supply: u32,
1488 ) -> DispatchResult {
1489 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1490 .map(|_| None)
1491 .or_else(|origin| ensure_signed(origin).map(Some))?;
1492
1493 ensure!(
1494 !CollectionMaxSupply::<T, I>::contains_key(&collection),
1495 Error::<T, I>::MaxSupplyAlreadySet
1496 );
1497
1498 let details =
1499 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1500 if let Some(check_owner) = &maybe_check_owner {
1501 ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
1502 }
1503
1504 ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
1505
1506 CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
1507 Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
1508 Ok(())
1509 }
1510
1511 #[pallet::call_index(24)]
1523 #[pallet::weight(T::WeightInfo::set_price())]
1524 pub fn set_price(
1525 origin: OriginFor<T>,
1526 collection: T::CollectionId,
1527 item: T::ItemId,
1528 price: Option<ItemPrice<T, I>>,
1529 whitelisted_buyer: Option<AccountIdLookupOf<T>>,
1530 ) -> DispatchResult {
1531 let origin = ensure_signed(origin)?;
1532 let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1533 Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1534 }
1535
1536 #[pallet::call_index(25)]
1546 #[pallet::weight(T::WeightInfo::buy_item())]
1547 pub fn buy_item(
1548 origin: OriginFor<T>,
1549 collection: T::CollectionId,
1550 item: T::ItemId,
1551 bid_price: ItemPrice<T, I>,
1552 ) -> DispatchResult {
1553 let origin = ensure_signed(origin)?;
1554 Self::do_buy_item(collection, item, origin, bid_price)
1555 }
1556 }
1557}