12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#![warn(missing_docs)]56#![cfg_attr(not(feature = "std"), no_std)]57extern crate alloc;5859use core::ops::{Deref, DerefMut};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 weights::Pays,69 transactional,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 COLLECTION_NUMBER_LIMIT,74 Collection,75 RpcCollection,76 CollectionId,77 CreateItemData,78 MAX_TOKEN_PREFIX_LENGTH,79 COLLECTION_ADMINS_LIMIT,80 TokenId,81 TokenChild,82 CollectionStats,83 MAX_TOKEN_OWNERSHIP,84 CollectionMode,85 NFT_SPONSOR_TRANSFER_TIMEOUT,86 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,88 MAX_SPONSOR_TIMEOUT,89 CUSTOM_DATA_LIMIT,90 CollectionLimits,91 CreateCollectionData,92 SponsorshipState,93 CreateItemExData,94 SponsoringRateLimit,95 budget::Budget,96 PhantomType,97 Property,98 Properties,99 PropertiesPermissionMap,100 PropertyKey,101 PropertyValue,102 PropertyPermission,103 PropertiesError,104 PropertyKeyPermission,105 TokenData,106 TrySetProperty,107 PropertyScope,108 109 RmrkCollectionInfo,110 RmrkInstanceInfo,111 RmrkResourceInfo,112 RmrkPropertyInfo,113 RmrkBaseInfo,114 RmrkPartType,115 RmrkBoundedTheme,116 RmrkNftChild,117 CollectionPermissions,118 SchemaVersion,119};120121pub use pallet::*;122use sp_core::H160;123use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod dispatch;127pub mod erc;128pub mod eth;129pub mod weights;130131132pub type SelfWeightOf<T> = <T as Config>::WeightInfo;133134135136#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]137pub struct CollectionHandle<T: Config> {138 139 pub id: CollectionId,140 collection: Collection<T::AccountId>,141 142 pub recorder: SubstrateRecorder<T>,143}144145impl<T: Config> WithRecorder<T> for CollectionHandle<T> {146 fn recorder(&self) -> &SubstrateRecorder<T> {147 &self.recorder148 }149 fn into_recorder(self) -> SubstrateRecorder<T> {150 self.recorder151 }152}153154impl<T: Config> CollectionHandle<T> {155 156 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {157 <CollectionById<T>>::get(id).map(|collection| Self {158 id,159 collection,160 recorder: SubstrateRecorder::new(gas_limit),161 })162 }163164 165 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {166 <CollectionById<T>>::get(id).map(|collection| Self {167 id,168 collection,169 recorder,170 })171 }172173 174 175 pub fn new(id: CollectionId) -> Option<Self> {176 Self::new_with_gas_limit(id, u64::MAX)177 }178179 180 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {181 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)182 }183184 185 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {186 self.recorder187 .consume_gas(T::GasWeightMapping::weight_to_gas(188 <T as frame_system::Config>::DbWeight::get()189 .read190 .saturating_mul(reads),191 ))192 }193194 195 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {196 self.recorder197 .consume_gas(T::GasWeightMapping::weight_to_gas(198 <T as frame_system::Config>::DbWeight::get()199 .write200 .saturating_mul(writes),201 ))202 }203204 205 pub fn save(self) -> DispatchResult {206 <CollectionById<T>>::insert(self.id, self.collection);207 Ok(())208 }209210 211 212 213 214 215 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {216 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);217 Ok(())218 }219220 221 222 223 224 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {225 if self.collection.sponsorship.pending_sponsor() != Some(sender) {226 return Ok(false);227 }228229 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());230 Ok(true)231 }232233 234 235 pub fn check_is_internal(&self) -> DispatchResult {236 if self.external_collection {237 return Err(<Error<T>>::CollectionIsExternal)?;238 }239240 Ok(())241 }242243 244 245 pub fn check_is_external(&self) -> DispatchResult {246 if !self.external_collection {247 return Err(<Error<T>>::CollectionIsInternal)?;248 }249250 Ok(())251 }252}253254impl<T: Config> Deref for CollectionHandle<T> {255 type Target = Collection<T::AccountId>;256257 fn deref(&self) -> &Self::Target {258 &self.collection259 }260}261262impl<T: Config> DerefMut for CollectionHandle<T> {263 fn deref_mut(&mut self) -> &mut Self::Target {264 &mut self.collection265 }266}267268impl<T: Config> CollectionHandle<T> {269 270 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {271 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);272 Ok(())273 }274275 276 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {277 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))278 }279280 281 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {282 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);283 Ok(())284 }285286 287 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {288 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)289 }290291 292 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {293 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)294 }295296 297 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {298 ensure!(299 <Allowlist<T>>::get((self.id, user)),300 <Error<T>>::AddressNotInAllowlist301 );302 Ok(())303 }304}305306#[frame_support::pallet]307pub mod pallet {308 use super::*;309 use pallet_evm::account;310 use dispatch::CollectionDispatch;311 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};312 use frame_system::pallet_prelude::*;313 use frame_support::traits::Currency;314 use up_data_structs::{TokenId, mapping::TokenAddressMapping};315 use scale_info::TypeInfo;316 use weights::WeightInfo;317318 #[pallet::config]319 pub trait Config:320 frame_system::Config321 + pallet_evm_coder_substrate::Config322 + pallet_evm::Config323 + TypeInfo324 + account::Config325 {326 327 type WeightInfo: WeightInfo;328329 330 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;331332 333 type Currency: Currency<Self::AccountId>;334335 336 #[pallet::constant]337 type CollectionCreationPrice: Get<338 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,339 >;340341 342 type CollectionDispatch: CollectionDispatch<Self>;343344 345 type TreasuryAccountId: Get<Self::AccountId>;346347 348 type ContractAddress: Get<H160>;349350 351 type EvmTokenAddressMapping: TokenAddressMapping<H160>;352353 354 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;355 }356357 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);358359 #[pallet::pallet]360 #[pallet::storage_version(STORAGE_VERSION)]361 #[pallet::generate_store(pub(super) trait Store)]362 pub struct Pallet<T>(_);363364 #[pallet::extra_constants]365 impl<T: Config> Pallet<T> {366 367 pub fn collection_admins_limit() -> u32 {368 COLLECTION_ADMINS_LIMIT369 }370 }371372 #[pallet::event]373 #[pallet::generate_deposit(pub fn deposit_event)]374 pub enum Event<T: Config> {375 376 CollectionCreated(377 378 CollectionId,379 380 u8,381 382 T::AccountId383 ),384385 386 CollectionDestroyed(387 388 CollectionId389 ),390391 392 ItemCreated(393 394 CollectionId,395 396 TokenId,397 398 T::CrossAccountId,399 400 u128401 ),402403 404 ItemDestroyed(405 406 CollectionId,407 408 TokenId,409 410 T::CrossAccountId,411 412 u128),413414 415 Transfer(416 417 CollectionId,418 419 TokenId,420 421 T::CrossAccountId,422 423 T::CrossAccountId,424 425 u128,426 ),427428 429 Approved(430 431 CollectionId,432 433 TokenId,434 435 T::CrossAccountId,436 437 T::CrossAccountId,438 439 u128,440 ),441442 443 CollectionPropertySet(444 445 CollectionId,446 447 PropertyKey448 ),449 450 451 CollectionPropertyDeleted(452 453 CollectionId,454 455 PropertyKey456 ),457 458 459 TokenPropertySet(460 461 CollectionId,462 463 TokenId,464 465 PropertyKey466 ),467 468 469 470 TokenPropertyDeleted(471 472 CollectionId,473 474 TokenId,475 476 PropertyKey477 ),478 479 480 PropertyPermissionSet(481 482 CollectionId,483 484 PropertyKey485 ),486 }487488 #[pallet::error]489 pub enum Error<T> {490 491 CollectionNotFound,492 493 MustBeTokenOwner,494 495 NoPermission,496 497 CantDestroyNotEmptyCollection,498 499 PublicMintingNotAllowed,500 501 AddressNotInAllowlist,502503 504 CollectionNameLimitExceeded,505 506 CollectionDescriptionLimitExceeded,507 508 CollectionTokenPrefixLimitExceeded,509 510 TotalCollectionsLimitExceeded,511 512 CollectionAdminCountExceeded,513 514 CollectionLimitBoundsExceeded,515 516 OwnerPermissionsCantBeReverted,517 518 TransferNotAllowed,519 520 AccountTokenLimitExceeded,521 522 CollectionTokenLimitExceeded,523 524 MetadataFlagFrozen,525526 527 TokenNotFound,528 529 TokenValueTooLow,530 531 ApprovedValueTooLow,532 533 CantApproveMoreThanOwned,534535 536 AddressIsZero,537 538 UnsupportedOperation,539540 541 NotSufficientFounds,542543 544 UserIsNotAllowedToNest,545 546 SourceCollectionIsNotAllowedToNest,547548 549 CollectionFieldSizeExceeded,550551 552 NoSpaceForProperty,553554 555 PropertyLimitReached,556557 558 PropertyKeyIsTooLong,559560 561 InvalidCharacterInPropertyKey,562563 564 EmptyPropertyKey,565566 567 CollectionIsExternal,568569 570 CollectionIsInternal,571 }572573 574 #[pallet::storage]575 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;576577 578 #[pallet::storage]579 pub type DestroyedCollectionCount<T> =580 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;581582 583 #[pallet::storage]584 pub type CollectionById<T> = StorageMap<585 Hasher = Blake2_128Concat,586 Key = CollectionId,587 Value = Collection<<T as frame_system::Config>::AccountId>,588 QueryKind = OptionQuery,589 >;590591 592 #[pallet::storage]593 #[pallet::getter(fn collection_properties)]594 pub type CollectionProperties<T> = StorageMap<595 Hasher = Blake2_128Concat,596 Key = CollectionId,597 Value = Properties,598 QueryKind = ValueQuery,599 OnEmpty = up_data_structs::CollectionProperties,600 >;601602 603 #[pallet::storage]604 #[pallet::getter(fn property_permissions)]605 pub type CollectionPropertyPermissions<T> = StorageMap<606 Hasher = Blake2_128Concat,607 Key = CollectionId,608 Value = PropertiesPermissionMap,609 QueryKind = ValueQuery,610 >;611612 613 #[pallet::storage]614 pub type AdminAmount<T> = StorageMap<615 Hasher = Blake2_128Concat,616 Key = CollectionId,617 Value = u32,618 QueryKind = ValueQuery,619 >;620621 622 #[pallet::storage]623 pub type IsAdmin<T: Config> = StorageNMap<624 Key = (625 Key<Blake2_128Concat, CollectionId>,626 Key<Blake2_128Concat, T::CrossAccountId>,627 ),628 Value = bool,629 QueryKind = ValueQuery,630 >;631632 633 #[pallet::storage]634 pub type Allowlist<T: Config> = StorageNMap<635 Key = (636 Key<Blake2_128Concat, CollectionId>,637 Key<Blake2_128Concat, T::CrossAccountId>,638 ),639 Value = bool,640 QueryKind = ValueQuery,641 >;642643 644 #[pallet::storage]645 pub type DummyStorageValue<T: Config> = StorageValue<646 Value = (647 CollectionStats,648 CollectionId,649 TokenId,650 TokenChild,651 PhantomType<(652 TokenData<T::CrossAccountId>,653 RpcCollection<T::AccountId>,654 655 RmrkCollectionInfo<T::AccountId>,656 RmrkInstanceInfo<T::AccountId>,657 RmrkResourceInfo,658 RmrkPropertyInfo,659 RmrkBaseInfo<T::AccountId>,660 RmrkPartType,661 RmrkBoundedTheme,662 RmrkNftChild,663 )>,664 ),665 QueryKind = OptionQuery,666 >;667668 #[pallet::hooks]669 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {670 fn on_runtime_upgrade() -> Weight {671 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {672 use up_data_structs::{CollectionVersion1, CollectionVersion2};673 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {674 let mut props = Vec::new();675 if !v.offchain_schema.is_empty() {676 props.push(Property {677 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),678 value: v679 .offchain_schema680 .clone()681 .into_inner()682 .try_into()683 .expect("offchain schema too big"),684 });685 }686 if !v.variable_on_chain_schema.is_empty() {687 props.push(Property {688 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),689 value: v690 .variable_on_chain_schema691 .clone()692 .into_inner()693 .try_into()694 .expect("offchain schema too big"),695 });696 }697 if !v.const_on_chain_schema.is_empty() {698 props.push(Property {699 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),700 value: v701 .const_on_chain_schema702 .clone()703 .into_inner()704 .try_into()705 .expect("offchain schema too big"),706 });707 }708 props.push(Property {709 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),710 value: match v.schema_version {711 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),712 SchemaVersion::Unique => b"Unique".as_slice(),713 }714 .to_vec()715 .try_into()716 .unwrap(),717 });718 Self::set_scoped_collection_properties(719 id,720 PropertyScope::None,721 props.into_iter(),722 )723 .expect("existing data larger than properties");724 let mut new = CollectionVersion2::from(v.clone());725 new.permissions.access = Some(v.access);726 new.permissions.mint_mode = Some(v.mint_mode);727 Some(new)728 });729 }730731 0732 }733 }734}735736impl<T: Config> Pallet<T> {737 738 739 740 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {741 ensure!(742 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,743 <Error<T>>::AddressIsZero744 );745 Ok(())746 }747748 749 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {750 <IsAdmin<T>>::iter_prefix((collection,))751 .map(|(a, _)| a)752 .collect()753 }754755 756 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {757 <Allowlist<T>>::iter_prefix((collection,))758 .map(|(a, _)| a)759 .collect()760 }761762 763 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {764 <Allowlist<T>>::get((collection, user))765 }766767 768 pub fn collection_stats() -> CollectionStats {769 let created = <CreatedCollectionCount<T>>::get();770 let destroyed = <DestroyedCollectionCount<T>>::get();771 CollectionStats {772 created: created.0,773 destroyed: destroyed.0,774 alive: created.0 - destroyed.0,775 }776 }777778 779 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {780 let collection = <CollectionById<T>>::get(collection);781 if collection.is_none() {782 return None;783 }784785 let collection = collection.unwrap();786 let limits = collection.limits;787 let effective_limits = CollectionLimits {788 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),789 sponsored_data_size: Some(limits.sponsored_data_size()),790 sponsored_data_rate_limit: Some(791 limits792 .sponsored_data_rate_limit793 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),794 ),795 token_limit: Some(limits.token_limit()),796 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(797 match collection.mode {798 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,799 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,800 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,801 },802 )),803 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),804 owner_can_transfer: Some(limits.owner_can_transfer()),805 owner_can_destroy: Some(limits.owner_can_destroy()),806 transfers_enabled: Some(limits.transfers_enabled()),807 };808809 Some(effective_limits)810 }811812 813 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {814 let Collection {815 name,816 description,817 owner,818 mode,819 token_prefix,820 sponsorship,821 limits,822 permissions,823 external_collection,824 } = <CollectionById<T>>::get(collection)?;825826 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)827 .into_iter()828 .map(|(key, permission)| PropertyKeyPermission { key, permission })829 .collect();830831 let properties = <CollectionProperties<T>>::get(collection)832 .into_iter()833 .map(|(key, value)| Property { key, value })834 .collect();835836 let permissions = CollectionPermissions {837 access: Some(permissions.access()),838 mint_mode: Some(permissions.mint_mode()),839 nesting: Some(permissions.nesting().clone()),840 };841842 Some(RpcCollection {843 name: name.into_inner(),844 description: description.into_inner(),845 owner,846 mode,847 token_prefix: token_prefix.into_inner(),848 sponsorship,849 limits,850 permissions,851 token_property_permissions,852 properties,853 read_only: external_collection,854 })855 }856}857858macro_rules! limit_default {859 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{860 $(861 if let Some($new) = $new.$field {862 let $old = $old.$field($($arg)?);863 let _ = $new;864 let _ = $old;865 $check866 } else {867 $new.$field = $old.$field868 }869 )*870 }};871}872macro_rules! limit_default_clone {873 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{874 $(875 if let Some($new) = $new.$field.clone() {876 let $old = $old.$field($($arg)?);877 let _ = $new;878 let _ = $old;879 $check880 } else {881 $new.$field = $old.$field.clone()882 }883 )*884 }};885}886887impl<T: Config> Pallet<T> {888 889 890 891 892 893 pub fn init_collection(894 owner: T::CrossAccountId,895 data: CreateCollectionData<T::AccountId>, 896 is_external: bool,897 ) -> Result<CollectionId, DispatchError> {898 {899 ensure!(900 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,901 Error::<T>::CollectionTokenPrefixLimitExceeded902 );903 }904905 let created_count = <CreatedCollectionCount<T>>::get()906 .0907 .checked_add(1)908 .ok_or(ArithmeticError::Overflow)?;909 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;910 let id = CollectionId(created_count);911912 913 ensure!(914 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,915 <Error<T>>::TotalCollectionsLimitExceeded916 );917918 919920 let collection = Collection {921 owner: owner.as_sub().clone(),922 name: data.name,923 mode: data.mode.clone(),924 description: data.description,925 token_prefix: data.token_prefix,926 sponsorship: data927 .pending_sponsor928 .map(SponsorshipState::Unconfirmed)929 .unwrap_or_default(),930 limits: data931 .limits932 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))933 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,934 permissions: data935 .permissions936 .map(|permissions| {937 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)938 })939 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,940 external_collection: is_external,941 };942943 let mut collection_properties = up_data_structs::CollectionProperties::get();944 collection_properties945 .try_set_from_iter(data.properties.into_iter())946 .map_err(<Error<T>>::from)?;947948 CollectionProperties::<T>::insert(id, collection_properties);949950 let mut token_props_permissions = PropertiesPermissionMap::new();951 token_props_permissions952 .try_set_from_iter(data.token_property_permissions.into_iter())953 .map_err(<Error<T>>::from)?;954955 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);956957 958 {959 let mut imbalance =960 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();961 imbalance.subsume(962 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(963 &T::TreasuryAccountId::get(),964 T::CollectionCreationPrice::get(),965 ),966 );967 <T as Config>::Currency::settle(968 &owner.as_sub(),969 imbalance,970 WithdrawReasons::TRANSFER,971 ExistenceRequirement::KeepAlive,972 )973 .map_err(|_| Error::<T>::NotSufficientFounds)?;974 }975976 <CreatedCollectionCount<T>>::put(created_count);977 <Pallet<T>>::deposit_event(Event::CollectionCreated(978 id,979 data.mode.id(),980 owner.as_sub().clone(),981 ));982 <PalletEvm<T>>::deposit_log(983 erc::CollectionHelpersEvents::CollectionCreated {984 owner: *owner.as_eth(),985 collection_id: eth::collection_id_to_address(id),986 }987 .to_log(T::ContractAddress::get()),988 );989 <CollectionById<T>>::insert(id, collection);990 Ok(id)991 }992993 994 995 996 997 pub fn destroy_collection(998 collection: CollectionHandle<T>,999 sender: &T::CrossAccountId,1000 ) -> DispatchResult {1001 ensure!(1002 collection.limits.owner_can_destroy(),1003 <Error<T>>::NoPermission,1004 );1005 collection.check_is_owner(sender)?;10061007 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1008 .01009 .checked_add(1)1010 .ok_or(ArithmeticError::Overflow)?;10111012 10131014 <DestroyedCollectionCount<T>>::put(destroyed_collections);1015 <CollectionById<T>>::remove(collection.id);1016 <AdminAmount<T>>::remove(collection.id);1017 <IsAdmin<T>>::remove_prefix((collection.id,), None);1018 <Allowlist<T>>::remove_prefix((collection.id,), None);1019 <CollectionProperties<T>>::remove(collection.id);10201021 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));1022 Ok(())1023 }10241025 1026 1027 1028 1029 1030 pub fn set_collection_property(1031 collection: &CollectionHandle<T>,1032 sender: &T::CrossAccountId,1033 property: Property,1034 ) -> DispatchResult {1035 collection.check_is_owner_or_admin(sender)?;10361037 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1038 let property = property.clone();1039 properties.try_set(property.key, property.value)1040 })1041 .map_err(<Error<T>>::from)?;10421043 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10441045 Ok(())1046 }10471048 1049 1050 1051 1052 1053 pub fn set_scoped_collection_property(1054 collection_id: CollectionId,1055 scope: PropertyScope,1056 property: Property,1057 ) -> DispatchResult {1058 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1059 properties.try_scoped_set(scope, property.key, property.value)1060 })1061 .map_err(<Error<T>>::from)?;1062 1063 Ok(())1064 }1065 1066 1067 1068 1069 1070 1071 pub fn set_scoped_collection_properties(1072 collection_id: CollectionId,1073 scope: PropertyScope,1074 properties: impl Iterator<Item = Property>,1075 ) -> DispatchResult {1076 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1077 stored_properties.try_scoped_set_from_iter(scope, properties)1078 })1079 .map_err(<Error<T>>::from)?;10801081 Ok(())1082 }10831084 1085 1086 1087 1088 1089 #[transactional]1090 pub fn set_collection_properties(1091 collection: &CollectionHandle<T>,1092 sender: &T::CrossAccountId,1093 properties: Vec<Property>,1094 ) -> DispatchResult {1095 for property in properties {1096 Self::set_collection_property(collection, sender, property)?;1097 }1098 1099 Ok(())1100 }1101 1102 1103 1104 1105 1106 1107 pub fn delete_collection_property(1108 collection: &CollectionHandle<T>,1109 sender: &T::CrossAccountId,1110 property_key: PropertyKey,1111 ) -> DispatchResult {1112 collection.check_is_owner_or_admin(sender)?;1113 1114 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1115 properties.remove(&property_key)1116 })1117 .map_err(<Error<T>>::from)?;1118 1119 Self::deposit_event(Event::CollectionPropertyDeleted(1120 collection.id,1121 property_key,1122 ));1123 1124 Ok(())1125 }1126 1127 1128 1129 1130 1131 1132 #[transactional]1133 pub fn delete_collection_properties(1134 collection: &CollectionHandle<T>,1135 sender: &T::CrossAccountId,1136 property_keys: Vec<PropertyKey>,1137 ) -> DispatchResult {1138 for key in property_keys {1139 Self::delete_collection_property(collection, sender, key)?;1140 }1141 1142 Ok(())1143 }1144 1145 1146 1147 1148 1149 1150 1151 pub fn set_property_permission_unchecked(1152 collection: CollectionId,1153 property_permission: PropertyKeyPermission,1154 ) -> DispatchResult {1155 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1156 permissions.try_set(property_permission.key, property_permission.permission)1157 })1158 .map_err(<Error<T>>::from)?;1159 Ok(())1160 }11611162 1163 1164 1165 1166 1167 pub fn set_property_permission(1168 collection: &CollectionHandle<T>,1169 sender: &T::CrossAccountId,1170 property_permission: PropertyKeyPermission,1171 ) -> DispatchResult {1172 collection.check_is_owner_or_admin(sender)?;11731174 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1175 let current_permission = all_permissions.get(&property_permission.key);1176 if matches![1177 current_permission,1178 Some(PropertyPermission { mutable: false, .. })1179 ] {1180 return Err(<Error<T>>::NoPermission.into());1181 }11821183 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1184 let property_permission = property_permission.clone();1185 permissions.try_set(property_permission.key, property_permission.permission)1186 })1187 .map_err(<Error<T>>::from)?;11881189 Self::deposit_event(Event::PropertyPermissionSet(1190 collection.id,1191 property_permission.key,1192 ));11931194 Ok(())1195 }11961197 1198 1199 1200 1201 1202 #[transactional]1203 pub fn set_token_property_permissions(1204 collection: &CollectionHandle<T>,1205 sender: &T::CrossAccountId,1206 property_permissions: Vec<PropertyKeyPermission>,1207 ) -> DispatchResult {1208 for prop_pemission in property_permissions {1209 Self::set_property_permission(collection, sender, prop_pemission)?;1210 }12111212 Ok(())1213 }12141215 1216 pub fn get_collection_property(1217 collection_id: CollectionId,1218 key: &PropertyKey,1219 ) -> Option<PropertyValue> {1220 Self::collection_properties(collection_id).get(key).cloned()1221 }12221223 1224 pub fn bytes_keys_to_property_keys(1225 keys: Vec<Vec<u8>>,1226 ) -> Result<Vec<PropertyKey>, DispatchError> {1227 keys.into_iter()1228 .map(|key| -> Result<PropertyKey, DispatchError> {1229 key.try_into()1230 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1231 })1232 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1233 }12341235 1236 pub fn filter_collection_properties(1237 collection_id: CollectionId,1238 keys: Option<Vec<PropertyKey>>,1239 ) -> Result<Vec<Property>, DispatchError> {1240 let properties = Self::collection_properties(collection_id);12411242 let properties = keys1243 .map(|keys| {1244 keys.into_iter()1245 .filter_map(|key| {1246 properties.get(&key).map(|value| Property {1247 key,1248 value: value.clone(),1249 })1250 })1251 .collect()1252 })1253 .unwrap_or_else(|| {1254 properties1255 .into_iter()1256 .map(|(key, value)| Property { key, value })1257 .collect()1258 });12591260 Ok(properties)1261 }12621263 1264 pub fn filter_property_permissions(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1268 let permissions = Self::property_permissions(collection_id);12691270 let key_permissions = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 permissions1275 .get(&key)1276 .map(|permission| PropertyKeyPermission {1277 key,1278 permission: permission.clone(),1279 })1280 })1281 .collect()1282 })1283 .unwrap_or_else(|| {1284 permissions1285 .into_iter()1286 .map(|(key, permission)| PropertyKeyPermission { key, permission })1287 .collect()1288 });12891290 Ok(key_permissions)1291 }12921293 1294 pub fn toggle_allowlist(1295 collection: &CollectionHandle<T>,1296 sender: &T::CrossAccountId,1297 user: &T::CrossAccountId,1298 allowed: bool,1299 ) -> DispatchResult {1300 collection.check_is_owner_or_admin(sender)?;13011302 13031304 if allowed {1305 <Allowlist<T>>::insert((collection.id, user), true);1306 } else {1307 <Allowlist<T>>::remove((collection.id, user));1308 }13091310 Ok(())1311 }13121313 1314 pub fn toggle_admin(1315 collection: &CollectionHandle<T>,1316 sender: &T::CrossAccountId,1317 user: &T::CrossAccountId,1318 admin: bool,1319 ) -> DispatchResult {1320 collection.check_is_owner(sender)?;13211322 let was_admin = <IsAdmin<T>>::get((collection.id, user));1323 if was_admin == admin {1324 return Ok(());1325 }1326 let amount = <AdminAmount<T>>::get(collection.id);13271328 if admin {1329 let amount = amount1330 .checked_add(1)1331 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1332 ensure!(1333 amount <= Self::collection_admins_limit(),1334 <Error<T>>::CollectionAdminCountExceeded,1335 );13361337 13381339 <AdminAmount<T>>::insert(collection.id, amount);1340 <IsAdmin<T>>::insert((collection.id, user), true);1341 } else {1342 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1343 <IsAdmin<T>>::remove((collection.id, user));1344 }13451346 Ok(())1347 }13481349 1350 pub fn clamp_limits(1351 mode: CollectionMode,1352 old_limit: &CollectionLimits,1353 mut new_limit: CollectionLimits,1354 ) -> Result<CollectionLimits, DispatchError> {1355 let limits = old_limit;1356 limit_default!(old_limit, new_limit,1357 account_token_ownership_limit => ensure!(1358 new_limit <= MAX_TOKEN_OWNERSHIP,1359 <Error<T>>::CollectionLimitBoundsExceeded,1360 ),1361 sponsored_data_size => ensure!(1362 new_limit <= CUSTOM_DATA_LIMIT,1363 <Error<T>>::CollectionLimitBoundsExceeded,1364 ),13651366 sponsored_data_rate_limit => {},1367 token_limit => ensure!(1368 old_limit >= new_limit && new_limit > 0,1369 <Error<T>>::CollectionTokenLimitExceeded1370 ),13711372 sponsor_transfer_timeout(match mode {1373 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1374 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1375 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1376 }) => ensure!(1377 new_limit <= MAX_SPONSOR_TIMEOUT,1378 <Error<T>>::CollectionLimitBoundsExceeded,1379 ),1380 sponsor_approve_timeout => {},1381 owner_can_transfer => ensure!(1382 !limits.owner_can_transfer_instaled() ||1383 old_limit || !new_limit,1384 <Error<T>>::OwnerPermissionsCantBeReverted,1385 ),1386 owner_can_destroy => ensure!(1387 old_limit || !new_limit,1388 <Error<T>>::OwnerPermissionsCantBeReverted,1389 ),1390 transfers_enabled => {},1391 );1392 Ok(new_limit)1393 }13941395 1396 pub fn clamp_permissions(1397 _mode: CollectionMode,1398 old_permission: &CollectionPermissions,1399 mut new_permission: CollectionPermissions,1400 ) -> Result<CollectionPermissions, DispatchError> {1401 limit_default_clone!(old_permission, new_permission,1402 access => {},1403 mint_mode => {},1404 nesting => { },1405 );1406 Ok(new_permission)1407 }1408}140914101411#[macro_export]1412macro_rules! unsupported {1413 () => {1414 Err(<Error<T>>::UnsupportedOperation.into())1415 };1416}141714181419pub trait CommonWeightInfo<CrossAccountId> {1420 1421 fn create_item() -> Weight;14221423 1424 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14251426 1427 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14281429 1430 fn burn_item() -> Weight;14311432 1433 1434 1435 fn set_collection_properties(amount: u32) -> Weight;14361437 1438 1439 1440 fn delete_collection_properties(amount: u32) -> Weight;14411442 1443 1444 1445 fn set_token_properties(amount: u32) -> Weight;14461447 1448 1449 1450 fn delete_token_properties(amount: u32) -> Weight;1451 1452 1453 1454 1455 1456 fn set_token_property_permissions(amount: u32) -> Weight;14571458 1459 fn transfer() -> Weight;14601461 1462 fn approve() -> Weight;14631464 1465 fn transfer_from() -> Weight;14661467 1468 fn burn_from() -> Weight;1469 1470 1471 1472 1473 1474 fn burn_recursively_self_raw() -> Weight;1475 1476 1477 1478 1479 fn burn_recursively_breadth_raw(amount: u32) -> Weight;1480 1481 1482 1483 1484 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1485 Self::burn_recursively_self_raw()1486 .saturating_mul(max_selfs.max(1) as u64)1487 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1488 }1489}149014911492pub trait RefungibleExtensionsWeightInfo {1493 1494 fn repartition() -> Weight;1495}149614971498149915001501pub trait CommonCollectionOperations<T: Config> {1502 1503 1504 1505 1506 1507 1508 fn create_item(1509 &self,1510 sender: T::CrossAccountId,1511 to: T::CrossAccountId,1512 data: CreateItemData,1513 nesting_budget: &dyn Budget,1514 ) -> DispatchResultWithPostInfo;15151516 1517 1518 1519 1520 1521 1522 fn create_multiple_items(1523 &self,1524 sender: T::CrossAccountId,1525 to: T::CrossAccountId,1526 data: Vec<CreateItemData>,1527 nesting_budget: &dyn Budget,1528 ) -> DispatchResultWithPostInfo;1529 1530 1531 1532 1533 1534 1535 1536 fn create_multiple_items_ex(1537 &self,1538 sender: T::CrossAccountId,1539 data: CreateItemExData<T::CrossAccountId>,1540 nesting_budget: &dyn Budget,1541 ) -> DispatchResultWithPostInfo;15421543 1544 1545 1546 1547 1548 fn burn_item(1549 &self,1550 sender: T::CrossAccountId,1551 token: TokenId,1552 amount: u128,1553 ) -> DispatchResultWithPostInfo;1554 1555 1556 1557 1558 1559 1560 1561 fn burn_item_recursively(1562 &self,1563 sender: T::CrossAccountId,1564 token: TokenId,1565 self_budget: &dyn Budget,1566 breadth_budget: &dyn Budget,1567 ) -> DispatchResultWithPostInfo;15681569 1570 1571 1572 1573 fn set_collection_properties(1574 &self,1575 sender: T::CrossAccountId,1576 properties: Vec<Property>,1577 ) -> DispatchResultWithPostInfo;15781579 1580 1581 1582 1583 fn delete_collection_properties(1584 &self,1585 sender: &T::CrossAccountId,1586 property_keys: Vec<PropertyKey>,1587 ) -> DispatchResultWithPostInfo;15881589 1590 1591 1592 1593 1594 1595 1596 1597 1598 fn set_token_properties(1599 &self,1600 sender: T::CrossAccountId,1601 token_id: TokenId,1602 properties: Vec<Property>,1603 budget: &dyn Budget,1604 ) -> DispatchResultWithPostInfo;16051606 1607 1608 1609 1610 1611 1612 1613 1614 1615 fn delete_token_properties(1616 &self,1617 sender: T::CrossAccountId,1618 token_id: TokenId,1619 property_keys: Vec<PropertyKey>,1620 budget: &dyn Budget,1621 ) -> DispatchResultWithPostInfo;16221623 1624 1625 1626 1627 1628 1629 fn set_token_property_permissions(1630 &self,1631 sender: &T::CrossAccountId,1632 property_permissions: Vec<PropertyKeyPermission>,1633 ) -> DispatchResultWithPostInfo;16341635 1636 1637 1638 1639 1640 1641 1642 fn transfer(1643 &self,1644 sender: T::CrossAccountId,1645 to: T::CrossAccountId,1646 token: TokenId,1647 amount: u128,1648 budget: &dyn Budget,1649 ) -> DispatchResultWithPostInfo;16501651 1652 1653 1654 1655 1656 1657 fn approve(1658 &self,1659 sender: T::CrossAccountId,1660 spender: T::CrossAccountId,1661 token: TokenId,1662 amount: u128,1663 ) -> DispatchResultWithPostInfo;1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 fn transfer_from(1676 &self,1677 sender: T::CrossAccountId,1678 from: T::CrossAccountId,1679 to: T::CrossAccountId,1680 token: TokenId,1681 amount: u128,1682 budget: &dyn Budget,1683 ) -> DispatchResultWithPostInfo;1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 fn burn_from(1695 &self,1696 sender: T::CrossAccountId,1697 from: T::CrossAccountId,1698 token: TokenId,1699 amount: u128,1700 budget: &dyn Budget,1701 ) -> DispatchResultWithPostInfo;17021703 1704 1705 1706 1707 1708 1709 fn check_nesting(1710 &self,1711 sender: T::CrossAccountId,1712 from: (CollectionId, TokenId),1713 under: TokenId,1714 budget: &dyn Budget,1715 ) -> DispatchResult;17161717 1718 1719 1720 1721 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17221723 1724 1725 1726 1727 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17281729 1730 1731 1732 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17331734 1735 fn collection_tokens(&self) -> Vec<TokenId>;17361737 1738 1739 1740 fn token_exists(&self, token: TokenId) -> bool;17411742 1743 fn last_token_id(&self) -> TokenId;17441745 1746 1747 1748 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17491750 1751 1752 1753 1754 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17551756 1757 1758 1759 1760 1761 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17621763 1764 fn total_supply(&self) -> u32;17651766 1767 1768 1769 fn account_balance(&self, account: T::CrossAccountId) -> u32;17701771 1772 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17731774 1775 fn total_pieces(&self, token: TokenId) -> Option<u128>;17761777 1778 1779 1780 1781 1782 fn allowance(1783 &self,1784 sender: T::CrossAccountId,1785 spender: T::CrossAccountId,1786 token: TokenId,1787 ) -> u128;17881789 1790 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1791}179217931794pub trait RefungibleExtensions<T>1795where1796 T: Config,1797{1798 1799 1800 1801 1802 1803 1804 1805 fn repartition(1806 &self,1807 sender: &T::CrossAccountId,1808 token: TokenId,1809 amount: u128,1810 ) -> DispatchResultWithPostInfo;1811}18121813181418151816pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1817 let post_info = PostDispatchInfo {1818 actual_weight: Some(weight),1819 pays_fee: Pays::Yes,1820 };1821 match res {1822 Ok(()) => Ok(post_info),1823 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1824 }1825}18261827impl<T: Config> From<PropertiesError> for Error<T> {1828 fn from(error: PropertiesError) -> Self {1829 match error {1830 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1831 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1832 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1833 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1834 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1835 }1836 }1837}