1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 CollectionField,59 PhantomType,60 Property,61 Properties,62 PropertiesPermissionMap,63 PropertyKey,64 PropertyValue,65 PropertyPermission,66 PropertiesError,67 PropertyKeyPermission,68 TokenData,69 TrySetProperty,70 PropertyScope,71 72 RmrkCollectionInfo,73 RmrkInstanceInfo,74 RmrkResourceInfo,75 RmrkPropertyInfo,76 RmrkBaseInfo,77 RmrkPartType,78 RmrkTheme,79 RmrkNftChild,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96 pub id: CollectionId,97 collection: Collection<T::AccountId>,98 pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101 fn recorder(&self) -> &SubstrateRecorder<T> {102 &self.recorder103 }104 fn into_recorder(self) -> SubstrateRecorder<T> {105 self.recorder106 }107}108impl<T: Config> CollectionHandle<T> {109 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110 <CollectionById<T>>::get(id).map(|collection| Self {111 id,112 collection,113 recorder: SubstrateRecorder::new(gas_limit),114 })115 }116 pub fn new(id: CollectionId) -> Option<Self> {117 Self::new_with_gas_limit(id, u64::MAX)118 }119 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {120 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)121 }122 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {123 self.recorder124 .consume_gas(T::GasWeightMapping::weight_to_gas(125 <T as frame_system::Config>::DbWeight::get()126 .read127 .saturating_mul(reads),128 ))129 }130 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {131 self.recorder132 .consume_gas(T::GasWeightMapping::weight_to_gas(133 <T as frame_system::Config>::DbWeight::get()134 .write135 .saturating_mul(writes),136 ))137 }138 pub fn save(self) -> DispatchResult {139 <CollectionById<T>>::insert(self.id, self.collection);140 Ok(())141 }142}143impl<T: Config> Deref for CollectionHandle<T> {144 type Target = Collection<T::AccountId>;145146 fn deref(&self) -> &Self::Target {147 &self.collection148 }149}150151impl<T: Config> DerefMut for CollectionHandle<T> {152 fn deref_mut(&mut self) -> &mut Self::Target {153 &mut self.collection154 }155}156157impl<T: Config> CollectionHandle<T> {158 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {159 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);160 Ok(())161 }162 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {163 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))164 }165 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {166 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);167 Ok(())168 }169 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {170 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)171 }172 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {173 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)174 }175 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {176 ensure!(177 <Allowlist<T>>::get((self.id, user)),178 <Error<T>>::AddressNotInAllowlist179 );180 Ok(())181 }182}183184#[frame_support::pallet]185pub mod pallet {186 use super::*;187 use pallet_evm::account;188 use dispatch::CollectionDispatch;189 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};190 use frame_system::pallet_prelude::*;191 use frame_support::traits::Currency;192 use up_data_structs::{TokenId, mapping::TokenAddressMapping};193 use scale_info::TypeInfo;194 use weights::WeightInfo;195196 #[pallet::config]197 pub trait Config:198 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config199 {200 type WeightInfo: WeightInfo;201 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;202203 type Currency: Currency<Self::AccountId>;204205 #[pallet::constant]206 type CollectionCreationPrice: Get<207 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,208 >;209 type CollectionDispatch: CollectionDispatch<Self>;210211 type TreasuryAccountId: Get<Self::AccountId>;212213 type EvmTokenAddressMapping: TokenAddressMapping<H160>;214 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;215 }216217 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);218219 #[pallet::pallet]220 #[pallet::storage_version(STORAGE_VERSION)]221 #[pallet::generate_store(pub(super) trait Store)]222 pub struct Pallet<T>(_);223224 #[pallet::extra_constants]225 impl<T: Config> Pallet<T> {226 pub fn collection_admins_limit() -> u32 {227 COLLECTION_ADMINS_LIMIT228 }229 }230231 #[pallet::event]232 #[pallet::generate_deposit(pub fn deposit_event)]233 pub enum Event<T: Config> {234 235 236 237 238 239 240 241 242 243 CollectionCreated(CollectionId, u8, T::AccountId),244245 246 247 248 249 250 CollectionDestroyed(CollectionId),251252 253 254 255 256 257 258 259 260 261 262 263 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),264265 266 267 268 269 270 271 272 273 274 275 276 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),277278 279 280 281 282 283 284 285 286 287 288 289 Transfer(290 CollectionId,291 TokenId,292 T::CrossAccountId,293 T::CrossAccountId,294 u128,295 ),296297 298 299 300 301 302 303 304 305 306 Approved(307 CollectionId,308 TokenId,309 T::CrossAccountId,310 T::CrossAccountId,311 u128,312 ),313314 CollectionPropertySet(CollectionId, PropertyKey),315316 CollectionPropertyDeleted(CollectionId, PropertyKey),317318 TokenPropertySet(CollectionId, TokenId, PropertyKey),319320 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),321322 PropertyPermissionSet(CollectionId, PropertyKey),323 }324325 #[pallet::error]326 pub enum Error<T> {327 328 CollectionNotFound,329 330 MustBeTokenOwner,331 332 NoPermission,333 334 PublicMintingNotAllowed,335 336 AddressNotInAllowlist,337338 339 CollectionNameLimitExceeded,340 341 CollectionDescriptionLimitExceeded,342 343 CollectionTokenPrefixLimitExceeded,344 345 TotalCollectionsLimitExceeded,346 347 CollectionAdminCountExceeded,348 349 CollectionLimitBoundsExceeded,350 351 OwnerPermissionsCantBeReverted,352 353 TransferNotAllowed,354 355 AccountTokenLimitExceeded,356 357 CollectionTokenLimitExceeded,358 359 MetadataFlagFrozen,360361 362 TokenNotFound,363 364 TokenValueTooLow,365 366 ApprovedValueTooLow,367 368 CantApproveMoreThanOwned,369370 371 AddressIsZero,372 373 UnsupportedOperation,374375 376 NotSufficientFounds,377378 379 NestingIsDisabled,380 381 OnlyOwnerAllowedToNest,382 383 SourceCollectionIsNotAllowedToNest,384385 386 CollectionFieldSizeExceeded,387388 389 NoSpaceForProperty,390391 392 PropertyLimitReached,393394 395 PropertyKeyIsTooLong,396397 398 InvalidCharacterInPropertyKey,399400 401 EmptyPropertyKey,402 }403404 #[pallet::storage]405 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;406 #[pallet::storage]407 pub type DestroyedCollectionCount<T> =408 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409410 411 #[pallet::storage]412 pub type CollectionById<T> = StorageMap<413 Hasher = Blake2_128Concat,414 Key = CollectionId,415 Value = Collection<<T as frame_system::Config>::AccountId>,416 QueryKind = OptionQuery,417 >;418419 420 #[pallet::storage]421 #[pallet::getter(fn collection_properties)]422 pub type CollectionProperties<T> = StorageMap<423 Hasher = Blake2_128Concat,424 Key = CollectionId,425 Value = Properties,426 QueryKind = ValueQuery,427 OnEmpty = up_data_structs::CollectionProperties,428 >;429430 #[pallet::storage]431 #[pallet::getter(fn property_permissions)]432 pub type CollectionPropertyPermissions<T> = StorageMap<433 Hasher = Blake2_128Concat,434 Key = CollectionId,435 Value = PropertiesPermissionMap,436 QueryKind = ValueQuery,437 >;438439 440 #[pallet::storage]441 pub type CollectionData<T> = StorageNMap<442 Key = (443 Key<Twox64Concat, CollectionId>,444 Key<Twox64Concat, CollectionField>,445 ),446 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,447 QueryKind = ValueQuery,448 >;449450 #[pallet::storage]451 pub type AdminAmount<T> = StorageMap<452 Hasher = Blake2_128Concat,453 Key = CollectionId,454 Value = u32,455 QueryKind = ValueQuery,456 >;457458 459 #[pallet::storage]460 pub type IsAdmin<T: Config> = StorageNMap<461 Key = (462 Key<Blake2_128Concat, CollectionId>,463 Key<Blake2_128Concat, T::CrossAccountId>,464 ),465 Value = bool,466 QueryKind = ValueQuery,467 >;468469 470 #[pallet::storage]471 pub type Allowlist<T: Config> = StorageNMap<472 Key = (473 Key<Blake2_128Concat, CollectionId>,474 Key<Blake2_128Concat, T::CrossAccountId>,475 ),476 Value = bool,477 QueryKind = ValueQuery,478 >;479480 481 #[pallet::storage]482 pub type DummyStorageValue<T: Config> = StorageValue<483 Value = (484 CollectionStats,485 CollectionId,486 TokenId,487 PhantomType<TokenData<T::CrossAccountId>>,488 PhantomType<RpcCollection<T::AccountId>>,489 490 PhantomType<RmrkCollectionInfo<T::AccountId>>,491 PhantomType<RmrkInstanceInfo<T::AccountId>>,492 PhantomType<RmrkResourceInfo>,493 PhantomType<RmrkPropertyInfo>,494 PhantomType<RmrkBaseInfo<T::AccountId>>,495 PhantomType<RmrkPartType>,496 PhantomType<RmrkTheme>,497 PhantomType<RmrkNftChild>,498 ),499 QueryKind = OptionQuery,500 >;501502 #[pallet::hooks]503 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {504 fn on_runtime_upgrade() -> Weight {505 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {506 use up_data_structs::{CollectionVersion1, CollectionVersion2};507 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {508 Self::set_field_raw(509 id,510 CollectionField::OffchainSchema,511 v.offchain_schema.clone().into_inner(),512 )513 .expect("data has lower bounds than field");514 Self::set_field_raw(515 id,516 CollectionField::ConstOnChainSchema,517 v.const_on_chain_schema.clone().into_inner(),518 )519 .expect("data has lower bounds than field");520521 Some(CollectionVersion2::from(v))522 });523 }524525 0526 }527 }528}529530impl<T: Config> Pallet<T> {531 532 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {533 ensure!(534 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,535 <Error<T>>::AddressIsZero536 );537 Ok(())538 }539 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {540 <IsAdmin<T>>::iter_prefix((collection,))541 .map(|(a, _)| a)542 .collect()543 }544 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {545 <Allowlist<T>>::iter_prefix((collection,))546 .map(|(a, _)| a)547 .collect()548 }549 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {550 <Allowlist<T>>::get((collection, user))551 }552 pub fn collection_stats() -> CollectionStats {553 let created = <CreatedCollectionCount<T>>::get();554 let destroyed = <DestroyedCollectionCount<T>>::get();555 CollectionStats {556 created: created.0,557 destroyed: destroyed.0,558 alive: created.0 - destroyed.0,559 }560 }561562 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {563 let collection = <CollectionById<T>>::get(collection);564 if collection.is_none() {565 return None;566 }567568 let collection = collection.unwrap();569 let limits = collection.limits;570 let effective_limits = CollectionLimits {571 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),572 sponsored_data_size: Some(limits.sponsored_data_size()),573 sponsored_data_rate_limit: Some(574 limits575 .sponsored_data_rate_limit576 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),577 ),578 token_limit: Some(limits.token_limit()),579 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(580 match collection.mode {581 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,582 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,583 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,584 },585 )),586 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),587 owner_can_transfer: Some(limits.owner_can_transfer()),588 owner_can_destroy: Some(limits.owner_can_destroy()),589 transfers_enabled: Some(limits.transfers_enabled()),590 nesting_rule: Some(limits.nesting_rule().clone()),591 };592593 Some(effective_limits)594 }595596 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {597 let Collection {598 name,599 description,600 owner,601 mode,602 access,603 token_prefix,604 mint_mode,605 schema_version,606 sponsorship,607 limits,608 } = <CollectionById<T>>::get(collection)?;609610 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)611 .iter()612 .map(|(key, permission)| PropertyKeyPermission {613 key: key.clone(),614 permission: permission.clone(),615 })616 .collect();617618 let properties = <CollectionProperties<T>>::get(collection)619 .iter()620 .map(|(key, value)| Property {621 key: key.clone(),622 value: value.clone(),623 })624 .collect();625626 Some(RpcCollection {627 name: name.into_inner(),628 description: description.into_inner(),629 owner,630 mode,631 access,632 token_prefix: token_prefix.into_inner(),633 mint_mode,634 schema_version,635 sponsorship,636 limits,637 offchain_schema: <CollectionData<T>>::get((638 collection,639 CollectionField::OffchainSchema,640 ))641 .into_inner(),642 const_on_chain_schema: <CollectionData<T>>::get((643 collection,644 CollectionField::ConstOnChainSchema,645 ))646 .into_inner(),647 token_property_permissions,648 properties,649 })650 }651}652653impl<T: Config> Pallet<T> {654 pub fn init_collection(655 owner: T::AccountId,656 data: CreateCollectionData<T::AccountId>,657 ) -> Result<CollectionId, DispatchError> {658 {659 ensure!(660 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,661 Error::<T>::CollectionTokenPrefixLimitExceeded662 );663 }664665 let created_count = <CreatedCollectionCount<T>>::get()666 .0667 .checked_add(1)668 .ok_or(ArithmeticError::Overflow)?;669 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;670 let id = CollectionId(created_count);671672 673 ensure!(674 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,675 <Error<T>>::TotalCollectionsLimitExceeded676 );677678 679680 let collection = Collection {681 owner: owner.clone(),682 name: data.name,683 mode: data.mode.clone(),684 mint_mode: false,685 access: data.access.unwrap_or_default(),686 description: data.description,687 token_prefix: data.token_prefix,688 schema_version: data.schema_version.unwrap_or_default(),689 sponsorship: data690 .pending_sponsor691 .map(SponsorshipState::Unconfirmed)692 .unwrap_or_default(),693 limits: data694 .limits695 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))696 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,697 };698699 let mut collection_properties = up_data_structs::CollectionProperties::get();700 collection_properties701 .try_set_from_iter(data.properties.into_iter())702 .map_err(<Error<T>>::from)?;703704 CollectionProperties::<T>::insert(id, collection_properties);705706 let mut token_props_permissions = PropertiesPermissionMap::new();707 token_props_permissions708 .try_set_from_iter(data.token_property_permissions.into_iter())709 .map_err(<Error<T>>::from)?;710711 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);712713 714 {715 let mut imbalance =716 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();717 imbalance.subsume(718 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(719 &T::TreasuryAccountId::get(),720 T::CollectionCreationPrice::get(),721 ),722 );723 <T as Config>::Currency::settle(724 &owner,725 imbalance,726 WithdrawReasons::TRANSFER,727 ExistenceRequirement::KeepAlive,728 )729 .map_err(|_| Error::<T>::NotSufficientFounds)?;730 }731732 <CreatedCollectionCount<T>>::put(created_count);733 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));734 <CollectionById<T>>::insert(id, collection);735 Self::set_field_raw(736 id,737 CollectionField::OffchainSchema,738 data.offchain_schema.into_inner(),739 )740 .expect("data has lower bounds than field");741 Self::set_field_raw(742 id,743 CollectionField::ConstOnChainSchema,744 data.const_on_chain_schema.into_inner(),745 )746 .expect("data has lower bounds than field");747 Ok(id)748 }749750 pub fn destroy_collection(751 collection: CollectionHandle<T>,752 sender: &T::CrossAccountId,753 ) -> DispatchResult {754 ensure!(755 collection.limits.owner_can_destroy(),756 <Error<T>>::NoPermission,757 );758 collection.check_is_owner(sender)?;759760 let destroyed_collections = <DestroyedCollectionCount<T>>::get()761 .0762 .checked_add(1)763 .ok_or(ArithmeticError::Overflow)?;764765 766767 <DestroyedCollectionCount<T>>::put(destroyed_collections);768 <CollectionById<T>>::remove(collection.id);769 <CollectionData<T>>::remove_prefix((collection.id,), None);770 <AdminAmount<T>>::remove(collection.id);771 <IsAdmin<T>>::remove_prefix((collection.id,), None);772 <Allowlist<T>>::remove_prefix((collection.id,), None);773 <CollectionProperties<T>>::remove(collection.id);774775 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));776 Ok(())777 }778779 pub fn set_collection_property(780 collection: &CollectionHandle<T>,781 sender: &T::CrossAccountId,782 property: Property,783 ) -> DispatchResult {784 collection.check_is_owner_or_admin(sender)?;785786 CollectionProperties::<T>::try_mutate(collection.id, |properties| {787 let property = property.clone();788 properties.try_set(property.key, property.value)789 })790 .map_err(<Error<T>>::from)?;791792 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));793794 Ok(())795 }796797 pub fn set_scoped_collection_property(798 collection: &CollectionHandle<T>,799 scope: PropertyScope,800 property: Property,801 ) -> DispatchResult {802 CollectionProperties::<T>::try_mutate(collection.id, |properties| {803 properties.try_scoped_set(scope, property.key, property.value)804 })805 .map_err(<Error<T>>::from)?;806807 Ok(())808 }809810 #[transactional]811 pub fn set_scoped_collection_properties(812 collection: &CollectionHandle<T>,813 scope: PropertyScope,814 properties: impl Iterator<Item = Property>,815 ) -> DispatchResult {816 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {817 stored_properties.try_scoped_set_from_iter(scope, properties)818 })819 .map_err(<Error<T>>::from)?;820821 Ok(())822 }823824 #[transactional]825 pub fn set_collection_properties(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 properties: Vec<Property>,829 ) -> DispatchResult {830 for property in properties {831 Self::set_collection_property(collection, sender, property)?;832 }833834 Ok(())835 }836837 pub fn delete_collection_property(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_key: PropertyKey,841 ) -> DispatchResult {842 collection.check_is_owner_or_admin(sender)?;843844 CollectionProperties::<T>::try_mutate(collection.id, |properties| {845 properties.remove(&property_key)846 })847 .map_err(<Error<T>>::from)?;848849 Self::deposit_event(Event::CollectionPropertyDeleted(850 collection.id,851 property_key,852 ));853854 Ok(())855 }856857 #[transactional]858 pub fn delete_collection_properties(859 collection: &CollectionHandle<T>,860 sender: &T::CrossAccountId,861 property_keys: Vec<PropertyKey>,862 ) -> DispatchResult {863 for key in property_keys {864 Self::delete_collection_property(collection, sender, key)?;865 }866867 Ok(())868 }869870 pub fn set_property_permission(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 property_permission: PropertyKeyPermission,874 ) -> DispatchResult {875 collection.check_is_owner_or_admin(sender)?;876877 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);878 let current_permission = all_permissions.get(&property_permission.key);879 if matches![880 current_permission,881 Some(PropertyPermission { mutable: false, .. })882 ] {883 return Err(<Error<T>>::NoPermission.into());884 }885886 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {887 let property_permission = property_permission.clone();888 permissions.try_set(property_permission.key, property_permission.permission)889 })890 .map_err(<Error<T>>::from)?;891892 Self::deposit_event(Event::PropertyPermissionSet(893 collection.id,894 property_permission.key,895 ));896897 Ok(())898 }899900 #[transactional]901 pub fn set_property_permissions(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 property_permissions: Vec<PropertyKeyPermission>,905 ) -> DispatchResult {906 for prop_pemission in property_permissions {907 Self::set_property_permission(collection, sender, prop_pemission)?;908 }909910 Ok(())911 }912913 pub fn get_collection_property(914 collection_id: CollectionId,915 key: &PropertyKey,916 ) -> Option<PropertyValue> {917 Self::collection_properties(collection_id).get(key).cloned()918 }919920 pub fn bytes_keys_to_property_keys(921 keys: Vec<Vec<u8>>,922 ) -> Result<Vec<PropertyKey>, DispatchError> {923 keys.into_iter()924 .map(|key| -> Result<PropertyKey, DispatchError> {925 key.try_into()926 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())927 })928 .collect::<Result<Vec<PropertyKey>, DispatchError>>()929 }930931 pub fn filter_collection_properties(932 collection_id: CollectionId,933 keys: Option<Vec<PropertyKey>>,934 ) -> Result<Vec<Property>, DispatchError> {935 let properties = Self::collection_properties(collection_id);936937 let properties = keys938 .map(|keys| {939 keys.into_iter()940 .filter_map(|key| {941 properties.get(&key).map(|value| Property {942 key,943 value: value.clone(),944 })945 })946 .collect()947 })948 .unwrap_or_else(|| {949 properties950 .iter()951 .map(|(key, value)| Property {952 key: key.clone(),953 value: value.clone(),954 })955 .collect()956 });957958 Ok(properties)959 }960961 pub fn filter_property_permissions(962 collection_id: CollectionId,963 keys: Option<Vec<PropertyKey>>,964 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {965 let permissions = Self::property_permissions(collection_id);966967 let key_permissions = keys968 .map(|keys| {969 keys.into_iter()970 .filter_map(|key| {971 permissions972 .get(&key)973 .map(|permission| PropertyKeyPermission {974 key,975 permission: permission.clone(),976 })977 })978 .collect()979 })980 .unwrap_or_else(|| {981 permissions982 .iter()983 .map(|(key, permission)| PropertyKeyPermission {984 key: key.clone(),985 permission: permission.clone(),986 })987 .collect()988 });989990 Ok(key_permissions)991 }992993 fn set_field_raw(994 collection_id: CollectionId,995 field: CollectionField,996 value: Vec<u8>,997 ) -> DispatchResult {998 if !value.is_empty() {999 <CollectionData<T>>::insert(1000 (collection_id, field),1001 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1002 )1003 } else {1004 <CollectionData<T>>::remove((collection_id, field));1005 }1006 Ok(())1007 }10081009 pub fn set_field(1010 collection: &CollectionHandle<T>,1011 sender: &T::CrossAccountId,1012 field: CollectionField,1013 value: Vec<u8>,1014 ) -> DispatchResult {1015 collection.check_is_owner_or_admin(sender)?;10161017 10181019 Self::set_field_raw(collection.id, field, value)1020 }10211022 pub fn toggle_allowlist(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 user: &T::CrossAccountId,1026 allowed: bool,1027 ) -> DispatchResult {1028 collection.check_is_owner_or_admin(sender)?;10291030 10311032 if allowed {1033 <Allowlist<T>>::insert((collection.id, user), true);1034 } else {1035 <Allowlist<T>>::remove((collection.id, user));1036 }10371038 Ok(())1039 }10401041 pub fn toggle_admin(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 user: &T::CrossAccountId,1045 admin: bool,1046 ) -> DispatchResult {1047 collection.check_is_owner_or_admin(sender)?;10481049 let was_admin = <IsAdmin<T>>::get((collection.id, user));1050 if was_admin == admin {1051 return Ok(());1052 }1053 let amount = <AdminAmount<T>>::get(collection.id);10541055 if admin {1056 let amount = amount1057 .checked_add(1)1058 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1059 ensure!(1060 amount <= Self::collection_admins_limit(),1061 <Error<T>>::CollectionAdminCountExceeded,1062 );10631064 10651066 <AdminAmount<T>>::insert(collection.id, amount);1067 <IsAdmin<T>>::insert((collection.id, user), true);1068 } else {1069 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1070 <IsAdmin<T>>::remove((collection.id, user));1071 }10721073 Ok(())1074 }10751076 pub fn clamp_limits(1077 mode: CollectionMode,1078 old_limit: &CollectionLimits,1079 mut new_limit: CollectionLimits,1080 ) -> Result<CollectionLimits, DispatchError> {1081 macro_rules! limit_default {1082 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1083 $(1084 if let Some($new) = $new.$field {1085 let $old = $old.$field($($arg)?);1086 let _ = $new;1087 let _ = $old;1088 $check1089 } else {1090 $new.$field = $old.$field1091 }1092 )*1093 }};1094 }10951096 limit_default!(old_limit, new_limit,1097 account_token_ownership_limit => ensure!(1098 new_limit <= MAX_TOKEN_OWNERSHIP,1099 <Error<T>>::CollectionLimitBoundsExceeded,1100 ),1101 sponsor_transfer_timeout(match mode {1102 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1105 }) => ensure!(1106 new_limit <= MAX_SPONSOR_TIMEOUT,1107 <Error<T>>::CollectionLimitBoundsExceeded,1108 ),1109 sponsored_data_size => ensure!(1110 new_limit <= CUSTOM_DATA_LIMIT,1111 <Error<T>>::CollectionLimitBoundsExceeded,1112 ),1113 token_limit => ensure!(1114 old_limit >= new_limit && new_limit > 0,1115 <Error<T>>::CollectionTokenLimitExceeded1116 ),1117 owner_can_transfer => ensure!(1118 old_limit || !new_limit,1119 <Error<T>>::OwnerPermissionsCantBeReverted,1120 ),1121 owner_can_destroy => ensure!(1122 old_limit || !new_limit,1123 <Error<T>>::OwnerPermissionsCantBeReverted,1124 ),1125 sponsored_data_rate_limit => {},1126 transfers_enabled => {},1127 );1128 Ok(new_limit)1129 }1130}11311132#[macro_export]1133macro_rules! unsupported {1134 () => {1135 Err(<Error<T>>::UnsupportedOperation.into())1136 };1137}113811391140pub trait CommonWeightInfo<CrossAccountId> {1141 fn create_item() -> Weight;1142 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1143 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1144 fn burn_item() -> Weight;1145 fn set_collection_properties(amount: u32) -> Weight;1146 fn delete_collection_properties(amount: u32) -> Weight;1147 fn set_token_properties(amount: u32) -> Weight;1148 fn delete_token_properties(amount: u32) -> Weight;1149 fn set_property_permissions(amount: u32) -> Weight;1150 fn transfer() -> Weight;1151 fn approve() -> Weight;1152 fn transfer_from() -> Weight;1153 fn burn_from() -> Weight;1154}11551156pub trait CommonCollectionOperations<T: Config> {1157 fn create_item(1158 &self,1159 sender: T::CrossAccountId,1160 to: T::CrossAccountId,1161 data: CreateItemData,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn create_multiple_items(1165 &self,1166 sender: T::CrossAccountId,1167 to: T::CrossAccountId,1168 data: Vec<CreateItemData>,1169 nesting_budget: &dyn Budget,1170 ) -> DispatchResultWithPostInfo;1171 fn create_multiple_items_ex(1172 &self,1173 sender: T::CrossAccountId,1174 data: CreateItemExData<T::CrossAccountId>,1175 nesting_budget: &dyn Budget,1176 ) -> DispatchResultWithPostInfo;1177 fn burn_item(1178 &self,1179 sender: T::CrossAccountId,1180 token: TokenId,1181 amount: u128,1182 ) -> DispatchResultWithPostInfo;1183 fn set_collection_properties(1184 &self,1185 sender: T::CrossAccountId,1186 properties: Vec<Property>,1187 ) -> DispatchResultWithPostInfo;1188 fn delete_collection_properties(1189 &self,1190 sender: &T::CrossAccountId,1191 property_keys: Vec<PropertyKey>,1192 ) -> DispatchResultWithPostInfo;1193 fn set_token_properties(1194 &self,1195 sender: T::CrossAccountId,1196 token_id: TokenId,1197 property: Vec<Property>,1198 ) -> DispatchResultWithPostInfo;1199 fn delete_token_properties(1200 &self,1201 sender: T::CrossAccountId,1202 token_id: TokenId,1203 property_keys: Vec<PropertyKey>,1204 ) -> DispatchResultWithPostInfo;1205 fn set_property_permissions(1206 &self,1207 sender: &T::CrossAccountId,1208 property_permissions: Vec<PropertyKeyPermission>,1209 ) -> DispatchResultWithPostInfo;1210 fn transfer(1211 &self,1212 sender: T::CrossAccountId,1213 to: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResultWithPostInfo;1218 fn approve(1219 &self,1220 sender: T::CrossAccountId,1221 spender: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 ) -> DispatchResultWithPostInfo;1225 fn transfer_from(1226 &self,1227 sender: T::CrossAccountId,1228 from: T::CrossAccountId,1229 to: T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;1234 fn burn_from(1235 &self,1236 sender: T::CrossAccountId,1237 from: T::CrossAccountId,1238 token: TokenId,1239 amount: u128,1240 nesting_budget: &dyn Budget,1241 ) -> DispatchResultWithPostInfo;12421243 fn check_nesting(1244 &self,1245 sender: T::CrossAccountId,1246 from: (CollectionId, TokenId),1247 under: TokenId,1248 budget: &dyn Budget,1249 ) -> DispatchResult;12501251 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1252 fn collection_tokens(&self) -> Vec<TokenId>;1253 fn token_exists(&self, token: TokenId) -> bool;1254 fn last_token_id(&self) -> TokenId;12551256 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1257 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1258 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1259 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1260 1261 fn total_supply(&self) -> u32;1262 1263 fn account_balance(&self, account: T::CrossAccountId) -> u32;1264 1265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1266 fn allowance(1267 &self,1268 sender: T::CrossAccountId,1269 spender: T::CrossAccountId,1270 token: TokenId,1271 ) -> u128;1272}127312741275pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1276 let post_info = PostDispatchInfo {1277 actual_weight: Some(weight),1278 pays_fee: Pays::Yes,1279 };1280 match res {1281 Ok(()) => Ok(post_info),1282 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1283 }1284}12851286impl<T: Config> From<PropertiesError> for Error<T> {1287 fn from(error: PropertiesError) -> Self {1288 match error {1289 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1290 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1291 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1292 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1293 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1294 }1295 }1296}