1#![cfg_attr(not(feature = "std"), no_std)]
79
80extern crate alloc;
81
82pub use pallet::*;
83
84pub trait MintWithoutDeposit<AccountId> {
90 type MetadataKey;
92 type MetadataValue;
94
95 fn mint_without_deposit(
97 collection: CollectionId,
98 item: ItemIndex,
99 to: AccountId,
100 metadata: alloc::vec::Vec<(Self::MetadataKey, Self::MetadataValue)>,
101 ) -> Result<InstanceId, sp_runtime::DispatchError>;
102}
103
104#[cfg(feature = "runtime-benchmarks")]
105mod benchmarking;
106
107#[cfg(test)]
108mod mock;
109
110#[cfg(test)]
111mod tests;
112
113pub mod extension;
114pub mod weights;
115
116#[frame_support::pallet]
117pub mod pallet {
118 use crate::weights::WeightInfo;
119 #[cfg(any(test, feature = "try-runtime"))]
120 use alloc::collections::BTreeMap;
121 use alloc::vec::Vec;
122 use frame_support::{
123 pallet_prelude::*,
124 traits::{tokens::Balance, Consideration, Footprint, IsSubType, UnixTime},
125 transactional,
126 };
127 use frame_system::pallet_prelude::*;
128 #[cfg(any(test, feature = "try-runtime"))]
129 use sp_runtime::TryRuntimeError;
130 use sp_runtime::{
131 traits::{CheckedAdd, CheckedSub, Convert, Zero},
132 transaction_validity::TransactionPriority,
133 ArithmeticError, DispatchError,
134 };
135
136 pub type CollectionId = u32;
137 pub type ItemIndex = u32;
139 pub type InstanceId = u64;
141 pub type BalanceOf<T> = <T as Config>::Balance;
142 pub type CollectionInfoOf<T> = CollectionInfo<
143 <T as frame_system::Config>::AccountId,
144 BalanceOf<T>,
145 <T as Config>::Consideration,
146 >;
147 pub type MetadataKeyOf<T> = BoundedVec<u8, <T as Config>::MaxKeyLen>;
148 pub type MetadataValueOf<T> = BoundedVec<u8, <T as Config>::MaxValueLen>;
149
150 #[derive(
152 Clone, PartialEq, Eq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen,
153 )]
154 pub struct MetadataEntry<Value, Balance> {
155 pub value: Value,
156 pub deposit: Balance,
158 }
159
160 #[derive(
162 CloneNoBound,
163 PartialEqNoBound,
164 EqNoBound,
165 DebugNoBound,
166 Encode,
167 Decode,
168 DecodeWithMemTracking,
169 TypeInfo,
170 MaxEncodedLen,
171 )]
172 #[scale_info(skip_type_params(StorageConsideration))]
173 pub struct CollectionInfo<AccountId: Member, Balance: Member, StorageConsideration: Member> {
174 pub owner: AccountId,
176 pub pending_owner: Option<AccountId>,
178 pub next_item_index: ItemIndex,
180 pub item_count: u32,
182 pub metadata_count: u32,
184 pub collection_deposit: Balance,
186 pub owner_deposit: Balance,
188 pub consideration: StorageConsideration,
190 }
191
192 #[derive(
194 CloneNoBound,
195 PartialEqNoBound,
196 EqNoBound,
197 DebugNoBound,
198 Encode,
199 Decode,
200 DecodeWithMemTracking,
201 TypeInfo,
202 MaxEncodedLen,
203 )]
204 pub struct ItemDefinition<Balance: Member> {
205 pub supply: u32,
207 pub live_supply: u32,
209 pub metadata_count: u32,
211 pub deposit: Balance,
213 }
214
215 #[derive(
217 Clone, PartialEq, Eq, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen,
218 )]
219 pub struct Nft {
220 pub instance: InstanceId,
221 pub collection: CollectionId,
222 pub item: ItemIndex,
223 pub minted_at: u64,
225 pub last_moved: u64,
227 pub state_nonce: u64,
232 }
233
234 #[derive(
236 Clone,
237 Copy,
238 PartialEq,
239 Eq,
240 Debug,
241 Encode,
242 Decode,
243 DecodeWithMemTracking,
244 TypeInfo,
245 MaxEncodedLen,
246 )]
247 pub struct LockInfo {
248 pub retries: u8,
250 pub until: u64,
252 }
253
254 #[pallet::storage]
256 pub type NextCollectionId<T> = StorageValue<_, CollectionId, ValueQuery>;
257
258 #[pallet::storage]
260 pub type Collections<T: Config> = StorageMap<
261 _,
262 Twox64Concat,
263 CollectionId,
264 CollectionInfo<T::AccountId, BalanceOf<T>, T::Consideration>,
265 >;
266
267 #[pallet::storage]
269 pub type ItemDefs<T: Config> = StorageDoubleMap<
270 _,
271 Twox64Concat,
272 CollectionId,
273 Twox64Concat,
274 ItemIndex,
275 ItemDefinition<BalanceOf<T>>,
276 >;
277
278 #[pallet::storage]
282 pub type CollectionMetadata<T: Config> = StorageDoubleMap<
283 _,
284 Twox64Concat,
285 CollectionId,
286 Blake2_128Concat,
287 MetadataKeyOf<T>,
288 MetadataEntry<MetadataValueOf<T>, BalanceOf<T>>,
289 >;
290
291 #[pallet::storage]
297 pub type ItemMetadata<T: Config> = StorageNMap<
298 _,
299 (
300 NMapKey<Twox64Concat, CollectionId>,
301 NMapKey<Twox64Concat, ItemIndex>,
302 NMapKey<Blake2_128Concat, MetadataKeyOf<T>>,
303 ),
304 MetadataEntry<MetadataValueOf<T>, BalanceOf<T>>,
305 >;
306
307 #[pallet::storage]
309 pub type NextInstanceId<T> = StorageValue<_, InstanceId, ValueQuery>;
310
311 #[pallet::storage]
313 pub type NftsByOwner<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, Nft>;
314
315 #[pallet::storage]
317 pub type Instances<T: Config> = StorageMap<_, Twox64Concat, InstanceId, T::AccountId>;
318
319 #[pallet::storage]
321 pub type InstanceDeposits<T: Config> = StorageMap<_, Twox64Concat, InstanceId, BalanceOf<T>>;
322
323 #[pallet::storage]
328 pub type InstanceMetadataCount<T: Config> =
329 StorageMap<_, Twox64Concat, InstanceId, u32, ValueQuery>;
330
331 #[pallet::storage]
333 pub type InstanceMetadata<T: Config> = StorageDoubleMap<
334 _,
335 Twox64Concat,
336 InstanceId,
337 Blake2_128Concat,
338 MetadataKeyOf<T>,
339 MetadataEntry<MetadataValueOf<T>, BalanceOf<T>>,
340 >;
341
342 #[pallet::storage]
347 pub type Locked<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, LockInfo>;
348
349 #[pallet::event]
350 #[pallet::generate_deposit(pub(super) fn deposit_event)]
351 pub enum Event<T: Config> {
352 CollectionCreated { collection: CollectionId, owner: T::AccountId },
354 CollectionOwnerNominated { collection: CollectionId, pending_owner: Option<T::AccountId> },
356 CollectionOwnerChanged {
358 collection: CollectionId,
359 old_owner: T::AccountId,
360 new_owner: T::AccountId,
361 },
362 CollectionDeleted { collection: CollectionId },
364 ItemDefined { collection: CollectionId, item: ItemIndex },
366 ItemDeleted { collection: CollectionId, item: ItemIndex },
368 Minted {
370 instance: InstanceId,
371 collection: CollectionId,
372 item: ItemIndex,
373 owner: T::AccountId,
374 },
375 Transferred { instance: InstanceId, from: T::AccountId, to: T::AccountId },
377 ForceTransferred { instance: InstanceId, from: T::AccountId, to: T::AccountId },
379 Burned { instance: InstanceId },
381 CollectionMetadataSet { collection: CollectionId, key: MetadataKeyOf<T> },
383 CollectionMetadataRemoved { collection: CollectionId, key: MetadataKeyOf<T> },
385 ItemMetadataSet { collection: CollectionId, item: ItemIndex, key: MetadataKeyOf<T> },
387 ItemMetadataRemoved { collection: CollectionId, item: ItemIndex, key: MetadataKeyOf<T> },
389 InstanceMetadataSet { instance: InstanceId, key: MetadataKeyOf<T> },
391 InstanceMetadataRemoved { instance: InstanceId, key: MetadataKeyOf<T> },
393 }
394
395 #[pallet::error]
396 pub enum Error<T> {
397 TooManyCollections,
399 UnknownCollection,
401 NoPermission,
403 AlreadyCollectionOwner,
405 NotPendingCollectionOwner,
407 TooManyItems,
409 UnknownItem,
411 ItemInUse,
413 ItemMetadataNotEmpty,
415 CollectionItemsNotEmpty,
417 CollectionMetadataNotEmpty,
419 DeletionInvariant,
421 AddressOccupied,
423 TooManyInstances,
425 SupplyOverflow,
427 UnknownInstance,
429 TooManyInstanceMetadata,
431 SelfTransfer,
433 StateNonceOverflow,
435 }
436
437 #[pallet::composite_enum]
439 pub enum HoldReason {
440 StorageDeposit,
442 }
443
444 #[pallet::pallet]
445 pub struct Pallet<T>(_);
446
447 #[pallet::hooks]
448 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
449 #[cfg(feature = "try-runtime")]
450 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
451 Self::do_try_state()
452 }
453 }
454
455 #[pallet::origin]
457 #[derive(
458 CloneNoBound,
459 PartialEqNoBound,
460 EqNoBound,
461 DebugNoBound,
462 Encode,
463 Decode,
464 DecodeWithMemTracking,
465 TypeInfo,
466 MaxEncodedLen,
467 )]
468 pub enum Origin<T: Config> {
469 Nft { owner: T::AccountId, nft: Nft },
472 }
473
474 #[pallet::config]
475 pub trait Config:
476 frame_system::Config<
477 RuntimeOrigin: Into<Result<Origin<Self>, Self::RuntimeOrigin>> + From<Origin<Self>>,
478 RuntimeCall: IsSubType<Call<Self>>,
479 > + Send
480 + Sync
481 {
482 #[allow(deprecated)]
483 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
484
485 type WeightInfo: crate::weights::WeightInfo;
487
488 type UnixTime: UnixTime;
490
491 type Balance: Balance;
493
494 type Consideration: Consideration<Self::AccountId, BalanceOf<Self>>;
500
501 type CollectionDeposit: Convert<Footprint, BalanceOf<Self>>;
503
504 type ItemDeposit: Convert<Footprint, BalanceOf<Self>>;
506
507 type InstanceDeposit: Convert<Footprint, BalanceOf<Self>>;
509
510 type MetadataDeposit: Convert<Footprint, BalanceOf<Self>>;
515
516 #[pallet::constant]
518 type MaxKeyLen: Get<u32>;
519
520 #[pallet::constant]
522 type MaxValueLen: Get<u32>;
523
524 #[pallet::constant]
528 type MaxInstanceMetadata: Get<u32>;
529
530 #[pallet::constant]
532 type LockPeriod: Get<u64>;
533
534 #[pallet::constant]
536 type MaxTransferPriority: Get<TransactionPriority>;
537 }
538
539 #[pallet::call]
540 impl<T: Config> Pallet<T> {
541 #[pallet::call_index(0)]
543 #[pallet::weight(T::WeightInfo::create_collection())]
544 pub fn create_collection(origin: OriginFor<T>) -> DispatchResult {
545 let owner = ensure_signed(origin)?;
546 Self::do_create_collection(owner).map(|_| ())
547 }
548
549 #[pallet::call_index(1)]
554 #[pallet::weight(T::WeightInfo::define_item(metadata.len() as u32))]
555 pub fn define_item(
556 origin: OriginFor<T>,
557 collection: CollectionId,
558 metadata: Vec<(MetadataKeyOf<T>, MetadataValueOf<T>)>,
559 ) -> DispatchResult {
560 let owner = ensure_signed(origin)?;
561 Self::do_define_item(owner, collection, metadata).map(|_| ())
562 }
563
564 #[pallet::call_index(2)]
572 #[pallet::weight(T::WeightInfo::mint(metadata.len() as u32))]
573 pub fn mint(
574 origin: OriginFor<T>,
575 collection: CollectionId,
576 item: ItemIndex,
577 to: T::AccountId,
578 metadata: Vec<(MetadataKeyOf<T>, MetadataValueOf<T>)>,
579 ) -> DispatchResult {
580 let owner = ensure_signed(origin)?;
581 Self::do_mint(owner, collection, item, to, metadata).map(|_| ())
582 }
583
584 #[pallet::call_index(3)]
586 #[pallet::weight(T::WeightInfo::transfer())]
587 #[pallet::feeless_if(|origin: &OriginFor<T>, _to: &T::AccountId| -> bool {
588 Pallet::<T>::is_nft_origin(origin)
589 })]
590 pub fn transfer(origin: OriginFor<T>, to: T::AccountId) -> DispatchResultWithPostInfo {
591 let Ok(Origin::Nft { owner, nft }) = origin.into() else {
592 return Err(DispatchError::BadOrigin.into());
593 };
594 ensure!(to != owner, Error::<T>::SelfTransfer);
595 ensure!(!NftsByOwner::<T>::contains_key(&to), Error::<T>::AddressOccupied);
596
597 let from = owner;
598 let state_nonce =
599 nft.state_nonce.checked_add(1).ok_or(Error::<T>::StateNonceOverflow)?;
600 let nft = Nft { last_moved: T::UnixTime::now().as_secs(), state_nonce, ..nft };
601 NftsByOwner::<T>::insert(&to, nft.clone());
602 Instances::<T>::insert(nft.instance, &to);
603 Self::deposit_event(Event::Transferred { instance: nft.instance, from, to });
604 Ok(Pays::No.into())
605 }
606
607 #[pallet::call_index(4)]
613 #[pallet::weight(T::WeightInfo::burn(T::MaxInstanceMetadata::get()))]
614 #[pallet::feeless_if(|origin: &OriginFor<T>| -> bool {
615 Pallet::<T>::is_nft_origin(origin)
616 })]
617 #[transactional]
618 pub fn burn(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
619 let Ok(Origin::Nft { owner: _, nft }) = origin.into() else {
620 return Err(DispatchError::BadOrigin.into());
621 };
622 let metadata_count = Self::do_burn(nft)?;
623 Ok((Some(T::WeightInfo::burn(metadata_count)), Pays::No).into())
624 }
625
626 #[pallet::call_index(5)]
631 #[pallet::weight(T::WeightInfo::nominate_collection_owner())]
632 pub fn nominate_collection_owner(
633 origin: OriginFor<T>,
634 collection: CollectionId,
635 pending_owner: Option<T::AccountId>,
636 ) -> DispatchResult {
637 let owner = ensure_signed(origin)?;
638 Collections::<T>::try_mutate(collection, |maybe_info| {
639 let info = maybe_info.as_mut().ok_or(Error::<T>::UnknownCollection)?;
640 ensure!(info.owner == owner, Error::<T>::NoPermission);
641 ensure!(
642 pending_owner.as_ref() != Some(&info.owner),
643 Error::<T>::AlreadyCollectionOwner
644 );
645 info.pending_owner = pending_owner.clone();
646 Ok::<_, DispatchError>(())
647 })?;
648 Self::deposit_event(Event::CollectionOwnerNominated { collection, pending_owner });
649 Ok(())
650 }
651
652 #[pallet::call_index(6)]
657 #[pallet::weight(T::WeightInfo::set_collection_metadata())]
658 #[transactional]
659 pub fn set_collection_metadata(
660 origin: OriginFor<T>,
661 collection: CollectionId,
662 key: MetadataKeyOf<T>,
663 value: Option<MetadataValueOf<T>>,
664 ) -> DispatchResult {
665 let owner = ensure_signed(origin)?;
666 Self::do_set_collection_metadata(&owner, collection, key, value)
667 }
668
669 #[pallet::call_index(7)]
675 #[pallet::weight(T::WeightInfo::set_item_metadata())]
676 #[transactional]
677 pub fn set_item_metadata(
678 origin: OriginFor<T>,
679 collection: CollectionId,
680 item: ItemIndex,
681 key: MetadataKeyOf<T>,
682 value: Option<MetadataValueOf<T>>,
683 ) -> DispatchResult {
684 let owner = ensure_signed(origin)?;
685 Self::do_set_item_metadata(&owner, collection, item, key, value)
686 }
687
688 #[pallet::call_index(8)]
694 #[pallet::weight(T::WeightInfo::claim_collection_ownership())]
695 #[transactional]
696 pub fn claim_collection_ownership(
697 origin: OriginFor<T>,
698 collection: CollectionId,
699 ) -> DispatchResult {
700 let new_owner = ensure_signed(origin)?;
701 let info = Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
702 ensure!(
703 info.pending_owner.as_ref() == Some(&new_owner),
704 Error::<T>::NotPendingCollectionOwner
705 );
706
707 let old_owner = info.owner.clone();
708 let info = Self::change_collection_owner(info, new_owner.clone())?;
709 Collections::<T>::insert(collection, info);
710 Self::deposit_event(Event::CollectionOwnerChanged { collection, old_owner, new_owner });
711 Ok(())
712 }
713
714 #[pallet::call_index(9)]
719 #[pallet::weight(T::WeightInfo::set_instance_metadata())]
720 #[transactional]
721 pub fn set_instance_metadata(
722 origin: OriginFor<T>,
723 instance: InstanceId,
724 key: MetadataKeyOf<T>,
725 value: Option<MetadataValueOf<T>>,
726 ) -> DispatchResult {
727 let owner = ensure_signed(origin)?;
728 Self::do_set_instance_metadata(&owner, instance, key, value, true)
729 }
730
731 #[pallet::call_index(10)]
737 #[pallet::weight(T::WeightInfo::force_burn(T::MaxInstanceMetadata::get()))]
738 #[transactional]
739 pub fn force_burn(
740 origin: OriginFor<T>,
741 instance: InstanceId,
742 ) -> DispatchResultWithPostInfo {
743 let owner = ensure_signed(origin)?;
744 let metadata_count = Self::do_force_burn(&owner, instance)?;
745 Ok(Some(T::WeightInfo::force_burn(metadata_count)).into())
746 }
747
748 #[pallet::call_index(11)]
753 #[pallet::weight(T::WeightInfo::delete_item())]
754 #[transactional]
755 pub fn delete_item(
756 origin: OriginFor<T>,
757 collection: CollectionId,
758 item: ItemIndex,
759 ) -> DispatchResult {
760 let owner = ensure_signed(origin)?;
761 Self::do_delete_item(&owner, collection, item)
762 }
763
764 #[pallet::call_index(12)]
769 #[pallet::weight(T::WeightInfo::delete_collection())]
770 #[transactional]
771 pub fn delete_collection(origin: OriginFor<T>, collection: CollectionId) -> DispatchResult {
772 let owner = ensure_signed(origin)?;
773 Self::do_delete_collection(&owner, collection)
774 }
775
776 #[pallet::call_index(13)]
783 #[pallet::weight(T::WeightInfo::force_transfer())]
784 #[transactional]
785 pub fn force_transfer(
786 origin: OriginFor<T>,
787 instance: InstanceId,
788 to: T::AccountId,
789 ) -> DispatchResult {
790 let owner = ensure_signed(origin)?;
791 Self::do_force_transfer(&owner, instance, to)
792 }
793 }
794
795 impl<T: Config> Pallet<T> {
796 fn is_nft_origin(origin: &OriginFor<T>) -> bool {
797 let converted: Result<Origin<T>, OriginFor<T>> = origin.clone().into();
798 matches!(converted, Ok(Origin::Nft { .. }))
799 }
800
801 fn increase_owner_deposit(
802 info: CollectionInfoOf<T>,
803 amount: BalanceOf<T>,
804 ) -> Result<CollectionInfoOf<T>, DispatchError> {
805 let total = info.owner_deposit.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
806 Self::update_owner_deposit(info, total)
807 }
808
809 fn decrease_owner_deposit(
810 info: CollectionInfoOf<T>,
811 amount: BalanceOf<T>,
812 ) -> Result<CollectionInfoOf<T>, DispatchError> {
813 let total =
814 info.owner_deposit.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?;
815 Self::update_owner_deposit(info, total)
816 }
817
818 fn replace_owner_deposit(
819 info: CollectionInfoOf<T>,
820 old: BalanceOf<T>,
821 new: BalanceOf<T>,
822 ) -> Result<CollectionInfoOf<T>, DispatchError> {
823 let total_without_old =
824 info.owner_deposit.checked_sub(&old).ok_or(ArithmeticError::Underflow)?;
825 let total = total_without_old.checked_add(&new).ok_or(ArithmeticError::Overflow)?;
826 Self::update_owner_deposit(info, total)
827 }
828
829 fn update_owner_deposit(
830 info: CollectionInfoOf<T>,
831 owner_deposit: BalanceOf<T>,
832 ) -> Result<CollectionInfoOf<T>, DispatchError> {
833 let CollectionInfo {
834 owner,
835 pending_owner,
836 next_item_index,
837 item_count,
838 metadata_count,
839 collection_deposit,
840 owner_deposit: _,
841 consideration,
842 } = info;
843 let consideration = consideration.update(&owner, owner_deposit)?;
844 Ok(CollectionInfo {
845 owner,
846 pending_owner,
847 next_item_index,
848 item_count,
849 metadata_count,
850 collection_deposit,
851 owner_deposit,
852 consideration,
853 })
854 }
855
856 fn change_collection_owner(
857 info: CollectionInfoOf<T>,
858 new_owner: T::AccountId,
859 ) -> Result<CollectionInfoOf<T>, DispatchError> {
860 let CollectionInfo {
861 owner,
862 pending_owner: _,
863 next_item_index,
864 item_count,
865 metadata_count,
866 collection_deposit,
867 owner_deposit,
868 consideration: old_consideration,
869 } = info;
870 let consideration = T::Consideration::new(&new_owner, owner_deposit)?;
873 old_consideration.drop(&owner)?;
874 Ok(CollectionInfo {
875 owner: new_owner,
876 pending_owner: None,
877 next_item_index,
878 item_count,
879 metadata_count,
880 collection_deposit,
881 owner_deposit,
882 consideration,
883 })
884 }
885
886 pub fn do_create_collection(owner: T::AccountId) -> Result<CollectionId, DispatchError> {
888 let collection = NextCollectionId::<T>::get();
889 let next_collection =
890 collection.checked_add(1).ok_or(Error::<T>::TooManyCollections)?;
891 let footprint = Footprint::from_mel::<CollectionInfoOf<T>>();
892 let collection_deposit = T::CollectionDeposit::convert(footprint);
893 let consideration = T::Consideration::new(&owner, collection_deposit)?;
894
895 NextCollectionId::<T>::put(next_collection);
896 Collections::<T>::insert(
897 collection,
898 CollectionInfo {
899 owner: owner.clone(),
900 pending_owner: None,
901 next_item_index: 0,
902 item_count: 0,
903 metadata_count: 0,
904 collection_deposit,
905 owner_deposit: collection_deposit,
906 consideration,
907 },
908 );
909 Self::deposit_event(Event::CollectionCreated { collection, owner });
910 Ok(collection)
911 }
912
913 #[transactional]
915 pub fn do_define_item(
916 owner: T::AccountId,
917 collection: CollectionId,
918 metadata: Vec<(MetadataKeyOf<T>, MetadataValueOf<T>)>,
919 ) -> Result<ItemIndex, DispatchError> {
920 let mut info =
921 Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
922 ensure!(info.owner == owner, Error::<T>::NoPermission);
923
924 let item = info.next_item_index;
925 info.next_item_index = item.checked_add(1).ok_or(Error::<T>::TooManyItems)?;
926 info.item_count = info.item_count.checked_add(1).ok_or(Error::<T>::TooManyItems)?;
927 let footprint = Footprint::from_mel::<ItemDefinition<BalanceOf<T>>>();
928 let deposit = T::ItemDeposit::convert(footprint);
929 info = Self::increase_owner_deposit(info, deposit)?;
930 let definition =
931 ItemDefinition { supply: 0, live_supply: 0, metadata_count: 0, deposit };
932
933 Collections::<T>::insert(collection, info);
934 ItemDefs::<T>::insert(collection, item, definition);
935 Self::deposit_event(Event::ItemDefined { collection, item });
936 for (key, value) in metadata {
937 Self::do_set_item_metadata(&owner, collection, item, key, Some(value))?;
938 }
939 Ok(item)
940 }
941
942 pub fn item_metadata_of(
946 collection: CollectionId,
947 item: ItemIndex,
948 key: &MetadataKeyOf<T>,
949 ) -> Option<MetadataValueOf<T>> {
950 ItemMetadata::<T>::get((collection, item, key.clone()))
951 .map(|entry| entry.value)
952 .or_else(|| Self::collection_metadata_of(collection, key))
953 }
954
955 pub fn instance_metadata_of(
959 instance: InstanceId,
960 key: &MetadataKeyOf<T>,
961 ) -> Option<MetadataValueOf<T>> {
962 let owner = Instances::<T>::get(instance)?;
963 let nft = NftsByOwner::<T>::get(owner)?;
964 if nft.instance != instance {
965 return None;
966 }
967 InstanceMetadata::<T>::get(instance, key)
968 .map(|entry| entry.value)
969 .or_else(|| Self::item_metadata_of(nft.collection, nft.item, key))
970 }
971
972 pub fn collection_metadata_of(
974 collection: CollectionId,
975 key: &MetadataKeyOf<T>,
976 ) -> Option<MetadataValueOf<T>> {
977 CollectionMetadata::<T>::get(collection, key).map(|entry| entry.value)
978 }
979
980 fn metadata_footprint(key: &MetadataKeyOf<T>, value: &MetadataValueOf<T>) -> Footprint {
981 Footprint::from_parts(
984 1,
985 key.len()
986 .saturating_add(value.len())
987 .saturating_add(BalanceOf::<T>::max_encoded_len()),
988 )
989 }
990
991 fn do_set_collection_metadata(
992 owner: &T::AccountId,
993 collection: CollectionId,
994 key: MetadataKeyOf<T>,
995 value: Option<MetadataValueOf<T>>,
996 ) -> DispatchResult {
997 let mut info =
998 Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
999 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1000
1001 match (CollectionMetadata::<T>::get(collection, &key), value) {
1002 (Some(entry), Some(value)) => {
1003 let deposit =
1004 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value));
1005 info = Self::replace_owner_deposit(info, entry.deposit, deposit)?;
1006 CollectionMetadata::<T>::insert(
1007 collection,
1008 &key,
1009 MetadataEntry { value, deposit },
1010 );
1011 Self::deposit_event(Event::CollectionMetadataSet { collection, key });
1012 },
1013 (None, Some(value)) => {
1014 info.metadata_count =
1015 info.metadata_count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
1016 let deposit =
1017 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value));
1018 info = Self::increase_owner_deposit(info, deposit)?;
1019 CollectionMetadata::<T>::insert(
1020 collection,
1021 &key,
1022 MetadataEntry { value, deposit },
1023 );
1024 Self::deposit_event(Event::CollectionMetadataSet { collection, key });
1025 },
1026 (Some(entry), None) => {
1027 info.metadata_count =
1028 info.metadata_count.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
1029 info = Self::decrease_owner_deposit(info, entry.deposit)?;
1030 CollectionMetadata::<T>::remove(collection, &key);
1031 Self::deposit_event(Event::CollectionMetadataRemoved { collection, key });
1032 },
1033 (None, None) => {},
1034 }
1035 Collections::<T>::insert(collection, info);
1036 Ok(())
1037 }
1038
1039 fn do_set_item_metadata(
1040 owner: &T::AccountId,
1041 collection: CollectionId,
1042 item: ItemIndex,
1043 key: MetadataKeyOf<T>,
1044 value: Option<MetadataValueOf<T>>,
1045 ) -> DispatchResult {
1046 let mut info =
1047 Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
1048 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1049 let mut definition =
1050 ItemDefs::<T>::get(collection, item).ok_or(Error::<T>::UnknownItem)?;
1051
1052 match (ItemMetadata::<T>::get((collection, item, key.clone())), value) {
1053 (Some(entry), Some(value)) => {
1054 let deposit =
1055 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value));
1056 info = Self::replace_owner_deposit(info, entry.deposit, deposit)?;
1057 ItemMetadata::<T>::insert(
1058 (collection, item, key.clone()),
1059 MetadataEntry { value, deposit },
1060 );
1061 Self::deposit_event(Event::ItemMetadataSet { collection, item, key });
1062 },
1063 (None, Some(value)) => {
1064 definition.metadata_count = definition
1065 .metadata_count
1066 .checked_add(1)
1067 .ok_or(ArithmeticError::Overflow)?;
1068 let deposit =
1069 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value));
1070 info = Self::increase_owner_deposit(info, deposit)?;
1071 ItemMetadata::<T>::insert(
1072 (collection, item, key.clone()),
1073 MetadataEntry { value, deposit },
1074 );
1075 ItemDefs::<T>::insert(collection, item, definition);
1076 Self::deposit_event(Event::ItemMetadataSet { collection, item, key });
1077 },
1078 (Some(entry), None) => {
1079 definition.metadata_count = definition
1080 .metadata_count
1081 .checked_sub(1)
1082 .ok_or(ArithmeticError::Underflow)?;
1083 info = Self::decrease_owner_deposit(info, entry.deposit)?;
1084 ItemMetadata::<T>::remove((collection, item, key.clone()));
1085 ItemDefs::<T>::insert(collection, item, definition);
1086 Self::deposit_event(Event::ItemMetadataRemoved { collection, item, key });
1087 },
1088 (None, None) => {},
1089 }
1090 Collections::<T>::insert(collection, info);
1091 Ok(())
1092 }
1093
1094 fn do_set_instance_metadata(
1095 owner: &T::AccountId,
1096 instance: InstanceId,
1097 key: MetadataKeyOf<T>,
1098 value: Option<MetadataValueOf<T>>,
1099 with_deposit: bool,
1100 ) -> DispatchResult {
1101 let purse = Instances::<T>::get(instance).ok_or(Error::<T>::UnknownInstance)?;
1102 let nft = NftsByOwner::<T>::get(purse).ok_or(Error::<T>::UnknownInstance)?;
1103 ensure!(nft.instance == instance, Error::<T>::UnknownInstance);
1104
1105 let mut info =
1106 Collections::<T>::get(nft.collection).ok_or(Error::<T>::UnknownCollection)?;
1107 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1108
1109 match (InstanceMetadata::<T>::get(instance, &key), value) {
1110 (Some(entry), Some(value)) => {
1111 let deposit = if with_deposit {
1112 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value))
1113 } else {
1114 Zero::zero()
1115 };
1116 info = Self::replace_owner_deposit(info, entry.deposit, deposit)?;
1117 InstanceMetadata::<T>::insert(instance, &key, MetadataEntry { value, deposit });
1118 Self::deposit_event(Event::InstanceMetadataSet { instance, key });
1119 },
1120 (None, Some(value)) => {
1121 let count = InstanceMetadataCount::<T>::get(instance);
1122 ensure!(
1123 count < T::MaxInstanceMetadata::get(),
1124 Error::<T>::TooManyInstanceMetadata
1125 );
1126 let next_count = count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
1127 let deposit = if with_deposit {
1128 T::MetadataDeposit::convert(Self::metadata_footprint(&key, &value))
1129 } else {
1130 Zero::zero()
1131 };
1132 info = Self::increase_owner_deposit(info, deposit)?;
1133 InstanceMetadata::<T>::insert(instance, &key, MetadataEntry { value, deposit });
1134 InstanceMetadataCount::<T>::insert(instance, next_count);
1135 Self::deposit_event(Event::InstanceMetadataSet { instance, key });
1136 },
1137 (Some(entry), None) => {
1138 let count = InstanceMetadataCount::<T>::get(instance);
1139 let next_count = count.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
1140 info = Self::decrease_owner_deposit(info, entry.deposit)?;
1141 InstanceMetadata::<T>::remove(instance, &key);
1142 InstanceMetadataCount::<T>::insert(instance, next_count);
1143 Self::deposit_event(Event::InstanceMetadataRemoved { instance, key });
1144 },
1145 (None, None) => {},
1146 }
1147 Collections::<T>::insert(nft.collection, info);
1148 Ok(())
1149 }
1150
1151 fn do_burn(nft: Nft) -> Result<u32, DispatchError> {
1152 let instance = nft.instance;
1153 let expected_metadata_count = InstanceMetadataCount::<T>::take(instance);
1154 let mut actual_metadata_count = 0u32;
1155 let mut deposit = InstanceDeposits::<T>::take(instance).unwrap_or_else(Zero::zero);
1156 for (_, entry) in InstanceMetadata::<T>::drain_prefix(instance) {
1157 actual_metadata_count = actual_metadata_count.saturating_add(1);
1158 deposit = deposit.checked_add(&entry.deposit).ok_or(ArithmeticError::Overflow)?;
1159 }
1160 debug_assert_eq!(actual_metadata_count, expected_metadata_count);
1161
1162 ItemDefs::<T>::try_mutate(nft.collection, nft.item, |maybe_definition| {
1163 let definition = maybe_definition.as_mut().ok_or(Error::<T>::UnknownItem)?;
1164 definition.live_supply =
1165 definition.live_supply.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
1166 Ok::<_, DispatchError>(())
1167 })?;
1168 let info =
1169 Collections::<T>::get(nft.collection).ok_or(Error::<T>::UnknownCollection)?;
1170 let info = Self::decrease_owner_deposit(info, deposit)?;
1171 Collections::<T>::insert(nft.collection, info);
1172 Instances::<T>::remove(instance);
1173 Self::deposit_event(Event::Burned { instance });
1174 Ok(actual_metadata_count)
1175 }
1176
1177 fn do_force_burn(owner: &T::AccountId, instance: InstanceId) -> Result<u32, DispatchError> {
1178 let purse = Instances::<T>::get(instance).ok_or(Error::<T>::UnknownInstance)?;
1179 let nft = NftsByOwner::<T>::get(&purse).ok_or(Error::<T>::UnknownInstance)?;
1180 ensure!(nft.instance == instance, Error::<T>::UnknownInstance);
1181 let info =
1182 Collections::<T>::get(nft.collection).ok_or(Error::<T>::UnknownCollection)?;
1183 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1184
1185 NftsByOwner::<T>::remove(&purse);
1186 Locked::<T>::remove(&purse);
1187 Self::do_burn(nft)
1188 }
1189
1190 fn do_force_transfer(
1191 owner: &T::AccountId,
1192 instance: InstanceId,
1193 to: T::AccountId,
1194 ) -> DispatchResult {
1195 let from = Instances::<T>::get(instance).ok_or(Error::<T>::UnknownInstance)?;
1196 let nft = NftsByOwner::<T>::get(&from).ok_or(Error::<T>::UnknownInstance)?;
1197 ensure!(nft.instance == instance, Error::<T>::UnknownInstance);
1198 let info =
1199 Collections::<T>::get(nft.collection).ok_or(Error::<T>::UnknownCollection)?;
1200 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1201 ensure!(to != from, Error::<T>::SelfTransfer);
1202 ensure!(!NftsByOwner::<T>::contains_key(&to), Error::<T>::AddressOccupied);
1203
1204 let state_nonce =
1205 nft.state_nonce.checked_add(1).ok_or(Error::<T>::StateNonceOverflow)?;
1206 let nft = Nft { last_moved: T::UnixTime::now().as_secs(), state_nonce, ..nft };
1207 NftsByOwner::<T>::remove(&from);
1208 Locked::<T>::remove(&from);
1209 NftsByOwner::<T>::insert(&to, nft);
1210 Instances::<T>::insert(instance, &to);
1211 Self::deposit_event(Event::ForceTransferred { instance, from, to });
1212 Ok(())
1213 }
1214
1215 fn do_delete_item(
1216 owner: &T::AccountId,
1217 collection: CollectionId,
1218 item: ItemIndex,
1219 ) -> DispatchResult {
1220 let mut info =
1221 Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
1222 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1223 let definition = ItemDefs::<T>::get(collection, item).ok_or(Error::<T>::UnknownItem)?;
1224 ensure!(definition.live_supply == 0, Error::<T>::ItemInUse);
1225 ensure!(definition.metadata_count == 0, Error::<T>::ItemMetadataNotEmpty);
1226 ensure!(
1227 ItemMetadata::<T>::iter_prefix((collection, item)).next().is_none(),
1228 Error::<T>::DeletionInvariant
1229 );
1230
1231 info.item_count = info.item_count.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
1232 info = Self::decrease_owner_deposit(info, definition.deposit)?;
1233 Collections::<T>::insert(collection, info);
1234 ItemDefs::<T>::remove(collection, item);
1235 Self::deposit_event(Event::ItemDeleted { collection, item });
1236 Ok(())
1237 }
1238
1239 fn do_delete_collection(owner: &T::AccountId, collection: CollectionId) -> DispatchResult {
1240 let info = Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
1241 ensure!(info.owner == *owner, Error::<T>::NoPermission);
1242 ensure!(info.item_count == 0, Error::<T>::CollectionItemsNotEmpty);
1243 ensure!(info.metadata_count == 0, Error::<T>::CollectionMetadataNotEmpty);
1244 ensure!(
1245 ItemDefs::<T>::iter_prefix(collection).next().is_none(),
1246 Error::<T>::DeletionInvariant
1247 );
1248 ensure!(
1249 CollectionMetadata::<T>::iter_prefix(collection).next().is_none(),
1250 Error::<T>::DeletionInvariant
1251 );
1252 ensure!(info.owner_deposit == info.collection_deposit, Error::<T>::DeletionInvariant);
1253
1254 let CollectionInfo { owner, consideration, .. } = info;
1255 consideration.drop(&owner)?;
1256 Collections::<T>::remove(collection);
1257 Self::deposit_event(Event::CollectionDeleted { collection });
1258 Ok(())
1259 }
1260
1261 pub fn do_mint(
1263 owner: T::AccountId,
1264 collection: CollectionId,
1265 item: ItemIndex,
1266 to: T::AccountId,
1267 metadata: Vec<(MetadataKeyOf<T>, MetadataValueOf<T>)>,
1268 ) -> Result<InstanceId, DispatchError> {
1269 let info = Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
1270 ensure!(info.owner == owner, Error::<T>::NoPermission);
1271 Self::do_mint_inner(collection, item, to, metadata, true)
1272 }
1273
1274 #[transactional]
1275 fn do_mint_inner(
1276 collection: CollectionId,
1277 item: ItemIndex,
1278 to: T::AccountId,
1279 metadata: Vec<(MetadataKeyOf<T>, MetadataValueOf<T>)>,
1280 with_deposit: bool,
1281 ) -> Result<InstanceId, DispatchError> {
1282 ensure!(
1283 metadata.len() <= T::MaxInstanceMetadata::get() as usize,
1284 Error::<T>::TooManyInstanceMetadata
1285 );
1286 let mut info =
1287 Collections::<T>::get(collection).ok_or(Error::<T>::UnknownCollection)?;
1288 let mut definition =
1289 ItemDefs::<T>::get(collection, item).ok_or(Error::<T>::UnknownItem)?;
1290 ensure!(!NftsByOwner::<T>::contains_key(&to), Error::<T>::AddressOccupied);
1291
1292 let instance = NextInstanceId::<T>::get();
1293 let next_instance = instance.checked_add(1).ok_or(Error::<T>::TooManyInstances)?;
1294 let next_supply = definition.supply.checked_add(1).ok_or(Error::<T>::SupplyOverflow)?;
1295 let next_live_supply =
1296 definition.live_supply.checked_add(1).ok_or(Error::<T>::SupplyOverflow)?;
1297 let now = T::UnixTime::now().as_secs();
1298 let nft =
1299 Nft { instance, collection, item, minted_at: now, last_moved: now, state_nonce: 0 };
1300 let instance_deposit = if with_deposit {
1301 let record_size = nft
1306 .encoded_size()
1307 .saturating_add(to.encoded_size().saturating_mul(2))
1308 .saturating_add(instance.encoded_size().saturating_mul(3))
1309 .saturating_add(BalanceOf::<T>::max_encoded_len())
1310 .saturating_add(u32::max_encoded_len());
1311 let deposit = T::InstanceDeposit::convert(Footprint::from_parts(4, record_size));
1312 info = Self::increase_owner_deposit(info, deposit)?;
1313 Some(deposit)
1314 } else {
1315 None
1316 };
1317
1318 definition.supply = next_supply;
1319 definition.live_supply = next_live_supply;
1320 NextInstanceId::<T>::put(next_instance);
1321 let collection_owner = info.owner.clone();
1322 Collections::<T>::insert(collection, info);
1323 ItemDefs::<T>::insert(collection, item, definition);
1324 NftsByOwner::<T>::insert(&to, nft);
1325 Instances::<T>::insert(instance, &to);
1326 InstanceMetadataCount::<T>::insert(instance, 0);
1327 if let Some(deposit) = instance_deposit {
1328 InstanceDeposits::<T>::insert(instance, deposit);
1329 }
1330 for (key, value) in metadata {
1331 Self::do_set_instance_metadata(
1332 &collection_owner,
1333 instance,
1334 key,
1335 Some(value),
1336 with_deposit,
1337 )?;
1338 }
1339 Self::deposit_event(Event::Minted { instance, collection, item, owner: to });
1340 Ok(instance)
1341 }
1342 }
1343
1344 impl<T: Config> crate::MintWithoutDeposit<T::AccountId> for Pallet<T> {
1345 type MetadataKey = MetadataKeyOf<T>;
1346 type MetadataValue = MetadataValueOf<T>;
1347
1348 fn mint_without_deposit(
1349 collection: CollectionId,
1350 item: ItemIndex,
1351 to: T::AccountId,
1352 metadata: Vec<(Self::MetadataKey, Self::MetadataValue)>,
1353 ) -> Result<InstanceId, DispatchError> {
1354 ensure!(Collections::<T>::contains_key(collection), Error::<T>::UnknownCollection);
1355 Self::do_mint_inner(collection, item, to, metadata, false)
1356 }
1357 }
1358
1359 #[cfg(any(test, feature = "try-runtime"))]
1360 impl<T: Config> Pallet<T> {
1361 pub(crate) fn do_try_state() -> Result<(), TryRuntimeError> {
1364 let next_collection = NextCollectionId::<T>::get();
1365 let next_instance = NextInstanceId::<T>::get();
1366 let mut expected_collection_deposits = BTreeMap::<CollectionId, BalanceOf<T>>::new();
1367
1368 for (collection, info) in Collections::<T>::iter() {
1369 if collection >= next_collection {
1370 return Err(TryRuntimeError::Other(
1371 "collection identifier is not below NextCollectionId",
1372 ));
1373 }
1374 expected_collection_deposits.insert(collection, info.collection_deposit);
1375 }
1376
1377 let mut actual_item_counts = BTreeMap::<CollectionId, u32>::new();
1378 for (collection, item, definition) in ItemDefs::<T>::iter() {
1379 let info = Collections::<T>::get(collection)
1380 .ok_or(TryRuntimeError::Other("ItemDefs entry has no matching collection"))?;
1381 if item >= info.next_item_index {
1382 return Err(TryRuntimeError::Other(
1383 "item index is not below the collection's next item index",
1384 ));
1385 }
1386 if definition.live_supply > definition.supply {
1387 return Err(TryRuntimeError::Other(
1388 "item live supply exceeds its minted supply",
1389 ));
1390 }
1391 let item_count = actual_item_counts.entry(collection).or_default();
1392 *item_count = item_count
1393 .checked_add(1)
1394 .ok_or(TryRuntimeError::Other("collection item count overflowed"))?;
1395 let expected = expected_collection_deposits
1396 .get_mut(&collection)
1397 .ok_or(TryRuntimeError::Other("ItemDefs entry has no deposit aggregate"))?;
1398 *expected = expected
1399 .checked_add(&definition.deposit)
1400 .ok_or(TryRuntimeError::Other("collection deposit aggregate overflowed"))?;
1401 }
1402 for (collection, info) in Collections::<T>::iter() {
1403 let actual = actual_item_counts.get(&collection).copied().unwrap_or_default();
1404 if info.item_count != actual {
1405 return Err(TryRuntimeError::Other(
1406 "collection item count does not match stored definitions",
1407 ));
1408 }
1409 }
1410
1411 let mut live_by_item = BTreeMap::<(CollectionId, ItemIndex), u64>::new();
1412 for (owner, nft) in NftsByOwner::<T>::iter() {
1413 if nft.instance >= next_instance {
1414 return Err(TryRuntimeError::Other("NFT instance is not below NextInstanceId"));
1415 }
1416 if Instances::<T>::get(nft.instance) != Some(owner) {
1417 return Err(TryRuntimeError::Other(
1418 "NftsByOwner entry has no matching Instances entry",
1419 ));
1420 }
1421 let definition = ItemDefs::<T>::get(nft.collection, nft.item)
1422 .ok_or(TryRuntimeError::Other("NFT has no matching item definition"))?;
1423 let live = live_by_item.entry((nft.collection, nft.item)).or_default();
1424 *live += 1;
1425 if *live > u64::from(definition.live_supply) {
1426 return Err(TryRuntimeError::Other(
1427 "item live supply is below its stored instance count",
1428 ));
1429 }
1430 }
1431 for (collection, item, definition) in ItemDefs::<T>::iter() {
1432 let actual = live_by_item.get(&(collection, item)).copied().unwrap_or_default();
1433 if u64::from(definition.live_supply) != actual {
1434 return Err(TryRuntimeError::Other(
1435 "item live supply does not match stored instances",
1436 ));
1437 }
1438 }
1439
1440 for (instance, owner) in Instances::<T>::iter() {
1441 if instance >= next_instance {
1442 return Err(TryRuntimeError::Other(
1443 "Instances identifier is not below NextInstanceId",
1444 ));
1445 }
1446 if !InstanceMetadataCount::<T>::contains_key(instance) {
1447 return Err(TryRuntimeError::Other(
1448 "live instance has no metadata count entry",
1449 ));
1450 }
1451 if !matches!(
1452 NftsByOwner::<T>::get(&owner),
1453 Some(nft) if nft.instance == instance
1454 ) {
1455 return Err(TryRuntimeError::Other(
1456 "Instances entry has no matching NftsByOwner entry",
1457 ));
1458 }
1459 }
1460
1461 for (instance, deposit) in InstanceDeposits::<T>::iter() {
1462 if instance >= next_instance {
1463 return Err(TryRuntimeError::Other(
1464 "InstanceDeposits identifier is not below NextInstanceId",
1465 ));
1466 }
1467 if !Instances::<T>::contains_key(instance) {
1468 return Err(TryRuntimeError::Other(
1469 "InstanceDeposits entry has no matching instance",
1470 ));
1471 }
1472 let owner = Instances::<T>::get(instance).ok_or(TryRuntimeError::Other(
1473 "InstanceDeposits entry has no matching owner",
1474 ))?;
1475 let nft = NftsByOwner::<T>::get(owner)
1476 .ok_or(TryRuntimeError::Other("InstanceDeposits entry has no matching NFT"))?;
1477 let expected = expected_collection_deposits.get_mut(&nft.collection).ok_or(
1478 TryRuntimeError::Other(
1479 "InstanceDeposits entry has no collection deposit aggregate",
1480 ),
1481 )?;
1482 *expected = expected
1483 .checked_add(&deposit)
1484 .ok_or(TryRuntimeError::Other("collection deposit aggregate overflowed"))?;
1485 }
1486
1487 let mut actual_instance_metadata_counts = BTreeMap::<InstanceId, u32>::new();
1488 for (instance, _, entry) in InstanceMetadata::<T>::iter() {
1489 if instance >= next_instance {
1490 return Err(TryRuntimeError::Other(
1491 "InstanceMetadata identifier is not below NextInstanceId",
1492 ));
1493 }
1494 let owner = Instances::<T>::get(instance).ok_or(TryRuntimeError::Other(
1495 "InstanceMetadata entry has no matching instance",
1496 ))?;
1497 let nft = NftsByOwner::<T>::get(owner)
1498 .ok_or(TryRuntimeError::Other("InstanceMetadata entry has no matching NFT"))?;
1499 if nft.instance != instance {
1500 return Err(TryRuntimeError::Other(
1501 "InstanceMetadata entry resolves to a different NFT",
1502 ));
1503 }
1504 let count = actual_instance_metadata_counts.entry(instance).or_default();
1505 *count = count
1506 .checked_add(1)
1507 .ok_or(TryRuntimeError::Other("instance metadata count overflowed"))?;
1508 if *count > T::MaxInstanceMetadata::get() {
1509 return Err(TryRuntimeError::Other(
1510 "instance metadata count exceeds configured maximum",
1511 ));
1512 }
1513 let expected = expected_collection_deposits.get_mut(&nft.collection).ok_or(
1514 TryRuntimeError::Other("InstanceMetadata entry has no deposit aggregate"),
1515 )?;
1516 *expected = expected
1517 .checked_add(&entry.deposit)
1518 .ok_or(TryRuntimeError::Other("collection deposit aggregate overflowed"))?;
1519 }
1520
1521 for (instance, declared_count) in InstanceMetadataCount::<T>::iter() {
1522 if !Instances::<T>::contains_key(instance) {
1523 return Err(TryRuntimeError::Other(
1524 "InstanceMetadataCount entry has no matching instance",
1525 ));
1526 }
1527 let actual_count =
1528 actual_instance_metadata_counts.get(&instance).copied().unwrap_or_default();
1529 if declared_count != actual_count {
1530 return Err(TryRuntimeError::Other(
1531 "instance metadata count does not match stored entries",
1532 ));
1533 }
1534 if declared_count > T::MaxInstanceMetadata::get() {
1535 return Err(TryRuntimeError::Other(
1536 "instance metadata count exceeds configured maximum",
1537 ));
1538 }
1539 }
1540
1541 for (owner, lock) in Locked::<T>::iter() {
1542 if !NftsByOwner::<T>::contains_key(owner) {
1543 return Err(TryRuntimeError::Other("Locked entry has no matching NFT"));
1544 }
1545 if lock.retries == 0 {
1546 return Err(TryRuntimeError::Other("Locked retry count must begin at one"));
1547 }
1548 }
1549
1550 let mut actual_collection_metadata_counts = BTreeMap::<CollectionId, u32>::new();
1551 for (collection, _, entry) in CollectionMetadata::<T>::iter() {
1552 if !Collections::<T>::contains_key(collection) {
1553 return Err(TryRuntimeError::Other(
1554 "CollectionMetadata entry has no matching collection",
1555 ));
1556 }
1557 let count = actual_collection_metadata_counts.entry(collection).or_default();
1558 *count = count
1559 .checked_add(1)
1560 .ok_or(TryRuntimeError::Other("collection metadata count overflowed"))?;
1561 let expected = expected_collection_deposits.get_mut(&collection).ok_or(
1562 TryRuntimeError::Other("CollectionMetadata entry has no deposit aggregate"),
1563 )?;
1564 *expected = expected
1565 .checked_add(&entry.deposit)
1566 .ok_or(TryRuntimeError::Other("collection deposit aggregate overflowed"))?;
1567 }
1568 for (collection, info) in Collections::<T>::iter() {
1569 let actual =
1570 actual_collection_metadata_counts.get(&collection).copied().unwrap_or_default();
1571 if info.metadata_count != actual {
1572 return Err(TryRuntimeError::Other(
1573 "collection metadata count does not match stored entries",
1574 ));
1575 }
1576 }
1577
1578 let mut actual_item_metadata_counts = BTreeMap::<(CollectionId, ItemIndex), u32>::new();
1579 for ((collection, item, _), entry) in ItemMetadata::<T>::iter() {
1580 if !Collections::<T>::contains_key(collection) {
1581 return Err(TryRuntimeError::Other(
1582 "ItemMetadata entry has no matching collection",
1583 ));
1584 }
1585 if !ItemDefs::<T>::contains_key(collection, item) {
1586 return Err(TryRuntimeError::Other(
1587 "ItemMetadata entry has no matching item definition",
1588 ));
1589 }
1590 let count = actual_item_metadata_counts.entry((collection, item)).or_default();
1591 *count = count
1592 .checked_add(1)
1593 .ok_or(TryRuntimeError::Other("item metadata count overflowed"))?;
1594 let expected = expected_collection_deposits
1595 .get_mut(&collection)
1596 .ok_or(TryRuntimeError::Other("ItemMetadata entry has no deposit aggregate"))?;
1597 *expected = expected
1598 .checked_add(&entry.deposit)
1599 .ok_or(TryRuntimeError::Other("collection deposit aggregate overflowed"))?;
1600 }
1601 for (collection, item, definition) in ItemDefs::<T>::iter() {
1602 let actual = actual_item_metadata_counts
1603 .get(&(collection, item))
1604 .copied()
1605 .unwrap_or_default();
1606 if definition.metadata_count != actual {
1607 return Err(TryRuntimeError::Other(
1608 "item metadata count does not match stored entries",
1609 ));
1610 }
1611 }
1612
1613 for (collection, info) in Collections::<T>::iter() {
1614 let expected = expected_collection_deposits
1615 .get(&collection)
1616 .ok_or(TryRuntimeError::Other("collection has no deposit aggregate"))?;
1617 if info.owner_deposit != *expected {
1618 return Err(TryRuntimeError::Other(
1619 "collection owner deposit does not match its stored components",
1620 ));
1621 }
1622 }
1623
1624 Ok(())
1625 }
1626 }
1627}