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 .into_iter()612 .map(|(key, permission)| PropertyKeyPermission {613 key,614 permission,615 })616 .collect();617618 let properties = <CollectionProperties<T>>::get(collection)619 .into_iter()620 .map(|(key, value)| Property {621 key,622 value,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_id: CollectionId,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 pub fn set_scoped_collection_properties(811 collection_id: CollectionId,812 scope: PropertyScope,813 properties: impl Iterator<Item = Property>,814 ) -> DispatchResult {815 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {816 stored_properties.try_scoped_set_from_iter(scope, properties)817 })818 .map_err(<Error<T>>::from)?;819820 Ok(())821 }822823 #[transactional]824 pub fn set_collection_properties(825 collection: &CollectionHandle<T>,826 sender: &T::CrossAccountId,827 properties: Vec<Property>,828 ) -> DispatchResult {829 for property in properties {830 Self::set_collection_property(collection, sender, property)?;831 }832833 Ok(())834 }835836 pub fn delete_collection_property(837 collection: &CollectionHandle<T>,838 sender: &T::CrossAccountId,839 property_key: PropertyKey,840 ) -> DispatchResult {841 collection.check_is_owner_or_admin(sender)?;842843 CollectionProperties::<T>::try_mutate(collection.id, |properties| {844 properties.remove(&property_key)845 })846 .map_err(<Error<T>>::from)?;847848 Self::deposit_event(Event::CollectionPropertyDeleted(849 collection.id,850 property_key,851 ));852853 Ok(())854 }855856 #[transactional]857 pub fn delete_collection_properties(858 collection: &CollectionHandle<T>,859 sender: &T::CrossAccountId,860 property_keys: Vec<PropertyKey>,861 ) -> DispatchResult {862 for key in property_keys {863 Self::delete_collection_property(collection, sender, key)?;864 }865866 Ok(())867 }868869 pub fn set_property_permission(870 collection: &CollectionHandle<T>,871 sender: &T::CrossAccountId,872 property_permission: PropertyKeyPermission,873 ) -> DispatchResult {874 collection.check_is_owner_or_admin(sender)?;875876 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);877 let current_permission = all_permissions.get(&property_permission.key);878 if matches![879 current_permission,880 Some(PropertyPermission { mutable: false, .. })881 ] {882 return Err(<Error<T>>::NoPermission.into());883 }884885 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {886 let property_permission = property_permission.clone();887 permissions.try_set(property_permission.key, property_permission.permission)888 })889 .map_err(<Error<T>>::from)?;890891 Self::deposit_event(Event::PropertyPermissionSet(892 collection.id,893 property_permission.key,894 ));895896 Ok(())897 }898899 #[transactional]900 pub fn set_property_permissions(901 collection: &CollectionHandle<T>,902 sender: &T::CrossAccountId,903 property_permissions: Vec<PropertyKeyPermission>,904 ) -> DispatchResult {905 for prop_pemission in property_permissions {906 Self::set_property_permission(collection, sender, prop_pemission)?;907 }908909 Ok(())910 }911912 pub fn get_collection_property(913 collection_id: CollectionId,914 key: &PropertyKey,915 ) -> Option<PropertyValue> {916 Self::collection_properties(collection_id).get(key).cloned()917 }918919 pub fn bytes_keys_to_property_keys(920 keys: Vec<Vec<u8>>,921 ) -> Result<Vec<PropertyKey>, DispatchError> {922 keys.into_iter()923 .map(|key| -> Result<PropertyKey, DispatchError> {924 key.try_into()925 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())926 })927 .collect::<Result<Vec<PropertyKey>, DispatchError>>()928 }929930 pub fn filter_collection_properties(931 collection_id: CollectionId,932 keys: Option<Vec<PropertyKey>>,933 ) -> Result<Vec<Property>, DispatchError> {934 let properties = Self::collection_properties(collection_id);935936 let properties = keys937 .map(|keys| {938 keys.into_iter()939 .filter_map(|key| {940 properties.get(&key).map(|value| Property {941 key,942 value: value.clone(),943 })944 })945 .collect()946 })947 .unwrap_or_else(|| {948 properties949 .into_iter()950 .map(|(key, value)| Property {951 key,952 value,953 })954 .collect()955 });956957 Ok(properties)958 }959960 pub fn filter_property_permissions(961 collection_id: CollectionId,962 keys: Option<Vec<PropertyKey>>,963 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {964 let permissions = Self::property_permissions(collection_id);965966 let key_permissions = keys967 .map(|keys| {968 keys.into_iter()969 .filter_map(|key| {970 permissions971 .get(&key)972 .map(|permission| PropertyKeyPermission {973 key,974 permission: permission.clone(),975 })976 })977 .collect()978 })979 .unwrap_or_else(|| {980 permissions981 .into_iter()982 .map(|(key, permission)| PropertyKeyPermission {983 key,984 permission,985 })986 .collect()987 });988989 Ok(key_permissions)990 }991992 fn set_field_raw(993 collection_id: CollectionId,994 field: CollectionField,995 value: Vec<u8>,996 ) -> DispatchResult {997 if !value.is_empty() {998 <CollectionData<T>>::insert(999 (collection_id, field),1000 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1001 )1002 } else {1003 <CollectionData<T>>::remove((collection_id, field));1004 }1005 Ok(())1006 }10071008 pub fn set_field(1009 collection: &CollectionHandle<T>,1010 sender: &T::CrossAccountId,1011 field: CollectionField,1012 value: Vec<u8>,1013 ) -> DispatchResult {1014 collection.check_is_owner_or_admin(sender)?;10151016 10171018 Self::set_field_raw(collection.id, field, value)1019 }10201021 pub fn toggle_allowlist(1022 collection: &CollectionHandle<T>,1023 sender: &T::CrossAccountId,1024 user: &T::CrossAccountId,1025 allowed: bool,1026 ) -> DispatchResult {1027 collection.check_is_owner_or_admin(sender)?;10281029 10301031 if allowed {1032 <Allowlist<T>>::insert((collection.id, user), true);1033 } else {1034 <Allowlist<T>>::remove((collection.id, user));1035 }10361037 Ok(())1038 }10391040 pub fn toggle_admin(1041 collection: &CollectionHandle<T>,1042 sender: &T::CrossAccountId,1043 user: &T::CrossAccountId,1044 admin: bool,1045 ) -> DispatchResult {1046 collection.check_is_owner_or_admin(sender)?;10471048 let was_admin = <IsAdmin<T>>::get((collection.id, user));1049 if was_admin == admin {1050 return Ok(());1051 }1052 let amount = <AdminAmount<T>>::get(collection.id);10531054 if admin {1055 let amount = amount1056 .checked_add(1)1057 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1058 ensure!(1059 amount <= Self::collection_admins_limit(),1060 <Error<T>>::CollectionAdminCountExceeded,1061 );10621063 10641065 <AdminAmount<T>>::insert(collection.id, amount);1066 <IsAdmin<T>>::insert((collection.id, user), true);1067 } else {1068 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1069 <IsAdmin<T>>::remove((collection.id, user));1070 }10711072 Ok(())1073 }10741075 pub fn clamp_limits(1076 mode: CollectionMode,1077 old_limit: &CollectionLimits,1078 mut new_limit: CollectionLimits,1079 ) -> Result<CollectionLimits, DispatchError> {1080 macro_rules! limit_default {1081 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1082 $(1083 if let Some($new) = $new.$field {1084 let $old = $old.$field($($arg)?);1085 let _ = $new;1086 let _ = $old;1087 $check1088 } else {1089 $new.$field = $old.$field1090 }1091 )*1092 }};1093 }10941095 limit_default!(old_limit, new_limit,1096 account_token_ownership_limit => ensure!(1097 new_limit <= MAX_TOKEN_OWNERSHIP,1098 <Error<T>>::CollectionLimitBoundsExceeded,1099 ),1100 sponsor_transfer_timeout(match mode {1101 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1102 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 }) => ensure!(1105 new_limit <= MAX_SPONSOR_TIMEOUT,1106 <Error<T>>::CollectionLimitBoundsExceeded,1107 ),1108 sponsored_data_size => ensure!(1109 new_limit <= CUSTOM_DATA_LIMIT,1110 <Error<T>>::CollectionLimitBoundsExceeded,1111 ),1112 token_limit => ensure!(1113 old_limit >= new_limit && new_limit > 0,1114 <Error<T>>::CollectionTokenLimitExceeded1115 ),1116 owner_can_transfer => ensure!(1117 old_limit || !new_limit,1118 <Error<T>>::OwnerPermissionsCantBeReverted,1119 ),1120 owner_can_destroy => ensure!(1121 old_limit || !new_limit,1122 <Error<T>>::OwnerPermissionsCantBeReverted,1123 ),1124 sponsored_data_rate_limit => {},1125 transfers_enabled => {},1126 );1127 Ok(new_limit)1128 }1129}11301131#[macro_export]1132macro_rules! unsupported {1133 () => {1134 Err(<Error<T>>::UnsupportedOperation.into())1135 };1136}113711381139pub trait CommonWeightInfo<CrossAccountId> {1140 fn create_item() -> Weight;1141 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1142 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1143 fn burn_item() -> Weight;1144 fn set_collection_properties(amount: u32) -> Weight;1145 fn delete_collection_properties(amount: u32) -> Weight;1146 fn set_token_properties(amount: u32) -> Weight;1147 fn delete_token_properties(amount: u32) -> Weight;1148 fn set_property_permissions(amount: u32) -> Weight;1149 fn transfer() -> Weight;1150 fn approve() -> Weight;1151 fn transfer_from() -> Weight;1152 fn burn_from() -> Weight;1153}11541155pub trait CommonCollectionOperations<T: Config> {1156 fn create_item(1157 &self,1158 sender: T::CrossAccountId,1159 to: T::CrossAccountId,1160 data: CreateItemData,1161 nesting_budget: &dyn Budget,1162 ) -> DispatchResultWithPostInfo;1163 fn create_multiple_items(1164 &self,1165 sender: T::CrossAccountId,1166 to: T::CrossAccountId,1167 data: Vec<CreateItemData>,1168 nesting_budget: &dyn Budget,1169 ) -> DispatchResultWithPostInfo;1170 fn create_multiple_items_ex(1171 &self,1172 sender: T::CrossAccountId,1173 data: CreateItemExData<T::CrossAccountId>,1174 nesting_budget: &dyn Budget,1175 ) -> DispatchResultWithPostInfo;1176 fn burn_item(1177 &self,1178 sender: T::CrossAccountId,1179 token: TokenId,1180 amount: u128,1181 ) -> DispatchResultWithPostInfo;1182 fn set_collection_properties(1183 &self,1184 sender: T::CrossAccountId,1185 properties: Vec<Property>,1186 ) -> DispatchResultWithPostInfo;1187 fn delete_collection_properties(1188 &self,1189 sender: &T::CrossAccountId,1190 property_keys: Vec<PropertyKey>,1191 ) -> DispatchResultWithPostInfo;1192 fn set_token_properties(1193 &self,1194 sender: T::CrossAccountId,1195 token_id: TokenId,1196 property: Vec<Property>,1197 ) -> DispatchResultWithPostInfo;1198 fn delete_token_properties(1199 &self,1200 sender: T::CrossAccountId,1201 token_id: TokenId,1202 property_keys: Vec<PropertyKey>,1203 ) -> DispatchResultWithPostInfo;1204 fn set_property_permissions(1205 &self,1206 sender: &T::CrossAccountId,1207 property_permissions: Vec<PropertyKeyPermission>,1208 ) -> DispatchResultWithPostInfo;1209 fn transfer(1210 &self,1211 sender: T::CrossAccountId,1212 to: T::CrossAccountId,1213 token: TokenId,1214 amount: u128,1215 nesting_budget: &dyn Budget,1216 ) -> DispatchResultWithPostInfo;1217 fn approve(1218 &self,1219 sender: T::CrossAccountId,1220 spender: T::CrossAccountId,1221 token: TokenId,1222 amount: u128,1223 ) -> DispatchResultWithPostInfo;1224 fn transfer_from(1225 &self,1226 sender: T::CrossAccountId,1227 from: T::CrossAccountId,1228 to: T::CrossAccountId,1229 token: TokenId,1230 amount: u128,1231 nesting_budget: &dyn Budget,1232 ) -> DispatchResultWithPostInfo;1233 fn burn_from(1234 &self,1235 sender: T::CrossAccountId,1236 from: T::CrossAccountId,1237 token: TokenId,1238 amount: u128,1239 nesting_budget: &dyn Budget,1240 ) -> DispatchResultWithPostInfo;12411242 fn check_nesting(1243 &self,1244 sender: T::CrossAccountId,1245 from: (CollectionId, TokenId),1246 under: TokenId,1247 budget: &dyn Budget,1248 ) -> DispatchResult;12491250 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1251 fn collection_tokens(&self) -> Vec<TokenId>;1252 fn token_exists(&self, token: TokenId) -> bool;1253 fn last_token_id(&self) -> TokenId;12541255 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1256 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1257 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1258 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1259 1260 fn total_supply(&self) -> u32;1261 1262 fn account_balance(&self, account: T::CrossAccountId) -> u32;1263 1264 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1265 fn allowance(1266 &self,1267 sender: T::CrossAccountId,1268 spender: T::CrossAccountId,1269 token: TokenId,1270 ) -> u128;1271}127212731274pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1275 let post_info = PostDispatchInfo {1276 actual_weight: Some(weight),1277 pays_fee: Pays::Yes,1278 };1279 match res {1280 Ok(()) => Ok(post_info),1281 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1282 }1283}12841285impl<T: Config> From<PropertiesError> for Error<T> {1286 fn from(error: PropertiesError) -> Self {1287 match error {1288 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1289 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1290 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1291 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1292 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1293 }1294 }1295}