1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission, TokenData, TrySet,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {79 self.recorder80 .consume_gas(T::GasWeightMapping::weight_to_gas(81 <T as frame_system::Config>::DbWeight::get()82 .read83 .saturating_mul(reads),84 ))85 }86 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {87 self.recorder88 .consume_gas(T::GasWeightMapping::weight_to_gas(89 <T as frame_system::Config>::DbWeight::get()90 .write91 .saturating_mul(writes),92 ))93 }94 pub fn save(self) -> DispatchResult {95 <CollectionById<T>>::insert(self.id, self.collection);96 Ok(())97 }98}99impl<T: Config> Deref for CollectionHandle<T> {100 type Target = Collection<T::AccountId>;101102 fn deref(&self) -> &Self::Target {103 &self.collection104 }105}106107impl<T: Config> DerefMut for CollectionHandle<T> {108 fn deref_mut(&mut self) -> &mut Self::Target {109 &mut self.collection110 }111}112113impl<T: Config> CollectionHandle<T> {114 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {115 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);116 Ok(())117 }118 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {119 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))120 }121 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {126 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)127 }128 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {129 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)130 }131 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {132 ensure!(133 <Allowlist<T>>::get((self.id, user)),134 <Error<T>>::AddressNotInAllowlist135 );136 Ok(())137 }138139 pub fn check_can_update_meta(140 &self,141 subject: &T::CrossAccountId,142 item_owner: &T::CrossAccountId,143 ) -> DispatchResult {144 match self.meta_update_permission {145 MetaUpdatePermission::ItemOwner => {146 ensure!(subject == item_owner, <Error<T>>::NoPermission);147 Ok(())148 }149 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),150 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),151 }152 }153}154155#[frame_support::pallet]156pub mod pallet {157 use super::*;158 use pallet_evm::account;159 use dispatch::CollectionDispatch;160 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};161 use frame_system::pallet_prelude::*;162 use frame_support::traits::Currency;163 use up_data_structs::{TokenId, mapping::TokenAddressMapping};164 use scale_info::TypeInfo;165166 #[pallet::config]167 pub trait Config:168 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config169 {170 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;171172 type Currency: Currency<Self::AccountId>;173174 #[pallet::constant]175 type CollectionCreationPrice: Get<176 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,177 >;178 type CollectionDispatch: CollectionDispatch<Self>;179180 type TreasuryAccountId: Get<Self::AccountId>;181182 type EvmTokenAddressMapping: TokenAddressMapping<H160>;183 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;184 }185186 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);187188 #[pallet::pallet]189 #[pallet::storage_version(STORAGE_VERSION)]190 #[pallet::generate_store(pub(super) trait Store)]191 pub struct Pallet<T>(_);192193 #[pallet::extra_constants]194 impl<T: Config> Pallet<T> {195 pub fn collection_admins_limit() -> u32 {196 COLLECTION_ADMINS_LIMIT197 }198 }199200 #[pallet::event]201 #[pallet::generate_deposit(pub fn deposit_event)]202 pub enum Event<T: Config> {203 204 205 206 207 208 209 210 211 212 CollectionCreated(CollectionId, u8, T::AccountId),213214 215 216 217 218 219 CollectionDestroyed(CollectionId),220221 222 223 224 225 226 227 228 229 230 231 232 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),233234 235 236 237 238 239 240 241 242 243 244 245 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),246247 248 249 250 251 252 253 254 255 256 257 258 Transfer(259 CollectionId,260 TokenId,261 T::CrossAccountId,262 T::CrossAccountId,263 u128,264 ),265266 267 268 269 270 271 272 273 274 275 Approved(276 CollectionId,277 TokenId,278 T::CrossAccountId,279 T::CrossAccountId,280 u128,281 ),282283 CollectionPropertySet(CollectionId, PropertyKey),284285 CollectionPropertyDeleted(CollectionId, PropertyKey),286287 TokenPropertySet(CollectionId, TokenId, PropertyKey),288289 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),290291 PropertyPermissionSet(CollectionId, PropertyKey),292 }293294 #[pallet::error]295 pub enum Error<T> {296 297 CollectionNotFound,298 299 MustBeTokenOwner,300 301 NoPermission,302 303 PublicMintingNotAllowed,304 305 AddressNotInAllowlist,306307 308 CollectionNameLimitExceeded,309 310 CollectionDescriptionLimitExceeded,311 312 CollectionTokenPrefixLimitExceeded,313 314 TotalCollectionsLimitExceeded,315 316 TokenVariableDataLimitExceeded,317 318 CollectionAdminCountExceeded,319 320 CollectionLimitBoundsExceeded,321 322 OwnerPermissionsCantBeReverted,323 324 TransferNotAllowed,325 326 AccountTokenLimitExceeded,327 328 CollectionTokenLimitExceeded,329 330 MetadataFlagFrozen,331332 333 TokenNotFound,334 335 TokenValueTooLow,336 337 ApprovedValueTooLow,338 339 CantApproveMoreThanOwned,340341 342 AddressIsZero,343 344 UnsupportedOperation,345346 347 NotSufficientFounds,348349 350 NestingIsDisabled,351 352 OnlyOwnerAllowedToNest,353 354 SourceCollectionIsNotAllowedToNest,355356 357 CollectionFieldSizeExceeded,358359 360 NoSpaceForProperty,361362 363 PropertyLimitReached,364365 366 UnableToReadUnboundedKeys,367368 369 InvalidCharacterInPropertyKey,370371 372 EmptyPropertyKey,373 }374375 #[pallet::storage]376 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;377 #[pallet::storage]378 pub type DestroyedCollectionCount<T> =379 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;380381 382 #[pallet::storage]383 pub type CollectionById<T> = StorageMap<384 Hasher = Blake2_128Concat,385 Key = CollectionId,386 Value = Collection<<T as frame_system::Config>::AccountId>,387 QueryKind = OptionQuery,388 >;389390 391 #[pallet::storage]392 #[pallet::getter(fn collection_properties)]393 pub type CollectionProperties<T> = StorageMap<394 Hasher = Blake2_128Concat,395 Key = CollectionId,396 Value = Properties,397 QueryKind = ValueQuery,398 OnEmpty = up_data_structs::CollectionProperties,399 >;400401 #[pallet::storage]402 #[pallet::getter(fn property_permissions)]403 pub type CollectionPropertyPermissions<T> = StorageMap<404 Hasher = Blake2_128Concat,405 Key = CollectionId,406 Value = PropertiesPermissionMap,407 QueryKind = ValueQuery,408 >;409410 411 #[pallet::storage]412 pub type CollectionData<T> = StorageNMap<413 Key = (414 Key<Twox64Concat, CollectionId>,415 Key<Twox64Concat, CollectionField>,416 ),417 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,418 QueryKind = ValueQuery,419 >;420421 #[pallet::storage]422 pub type AdminAmount<T> = StorageMap<423 Hasher = Blake2_128Concat,424 Key = CollectionId,425 Value = u32,426 QueryKind = ValueQuery,427 >;428429 430 #[pallet::storage]431 pub type IsAdmin<T: Config> = StorageNMap<432 Key = (433 Key<Blake2_128Concat, CollectionId>,434 Key<Blake2_128Concat, T::CrossAccountId>,435 ),436 Value = bool,437 QueryKind = ValueQuery,438 >;439440 441 #[pallet::storage]442 pub type Allowlist<T: Config> = StorageNMap<443 Key = (444 Key<Blake2_128Concat, CollectionId>,445 Key<Blake2_128Concat, T::CrossAccountId>,446 ),447 Value = bool,448 QueryKind = ValueQuery,449 >;450451 452 #[pallet::storage]453 pub type DummyStorageValue<T: Config> = StorageValue<454 Value = (455 CollectionStats,456 CollectionId,457 TokenId,458 PhantomType<TokenData<T::CrossAccountId>>,459 PhantomType<RpcCollection<T::AccountId>>,460 ),461 QueryKind = OptionQuery,462 >;463464 #[pallet::hooks]465 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {466 fn on_runtime_upgrade() -> Weight {467 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {468 use up_data_structs::{CollectionVersion1, CollectionVersion2};469 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {470 Self::set_field_raw(471 id,472 CollectionField::OffchainSchema,473 v.offchain_schema.clone().into_inner(),474 )475 .expect("data has lower bounds than field");476 Self::set_field_raw(477 id,478 CollectionField::ConstOnChainSchema,479 v.const_on_chain_schema.clone().into_inner(),480 )481 .expect("data has lower bounds than field");482483 Some(CollectionVersion2::from(v))484 });485 }486487 0488 }489 }490}491492impl<T: Config> Pallet<T> {493 494 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {495 ensure!(496 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,497 <Error<T>>::AddressIsZero498 );499 Ok(())500 }501 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {502 <IsAdmin<T>>::iter_prefix((collection,))503 .map(|(a, _)| a)504 .collect()505 }506 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {507 <Allowlist<T>>::iter_prefix((collection,))508 .map(|(a, _)| a)509 .collect()510 }511 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {512 <Allowlist<T>>::get((collection, user))513 }514 pub fn collection_stats() -> CollectionStats {515 let created = <CreatedCollectionCount<T>>::get();516 let destroyed = <DestroyedCollectionCount<T>>::get();517 CollectionStats {518 created: created.0,519 destroyed: destroyed.0,520 alive: created.0 - destroyed.0,521 }522 }523524 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {525 let collection = <CollectionById<T>>::get(collection);526 if collection.is_none() {527 return None;528 }529530 let collection = collection.unwrap();531 let limits = collection.limits;532 let effective_limits = CollectionLimits {533 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),534 sponsored_data_size: Some(limits.sponsored_data_size()),535 sponsored_data_rate_limit: Some(536 limits537 .sponsored_data_rate_limit538 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),539 ),540 token_limit: Some(limits.token_limit()),541 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(542 match collection.mode {543 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,544 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,545 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,546 },547 )),548 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),549 owner_can_transfer: Some(limits.owner_can_transfer()),550 owner_can_destroy: Some(limits.owner_can_destroy()),551 transfers_enabled: Some(limits.transfers_enabled()),552 nesting_rule: Some(limits.nesting_rule().clone()),553 };554555 Some(effective_limits)556 }557558 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {559 let Collection {560 name,561 description,562 owner,563 mode,564 access,565 token_prefix,566 mint_mode,567 schema_version,568 sponsorship,569 limits,570 meta_update_permission,571 } = <CollectionById<T>>::get(collection)?;572573 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)574 .iter()575 .map(|(key, permission)| PropertyKeyPermission {576 key: key.clone(),577 permission: permission.clone(),578 })579 .collect();580581 let properties = <CollectionProperties<T>>::get(collection)582 .iter()583 .map(|(key, value)| Property {584 key: key.clone(),585 value: value.clone(),586 })587 .collect();588589 Some(RpcCollection {590 name: name.into_inner(),591 description: description.into_inner(),592 owner,593 mode,594 access,595 token_prefix: token_prefix.into_inner(),596 mint_mode,597 schema_version,598 sponsorship,599 limits,600 meta_update_permission,601 offchain_schema: <CollectionData<T>>::get((602 collection,603 CollectionField::OffchainSchema,604 ))605 .into_inner(),606 const_on_chain_schema: <CollectionData<T>>::get((607 collection,608 CollectionField::ConstOnChainSchema,609 ))610 .into_inner(),611 token_property_permissions,612 properties,613 })614 }615}616617impl<T: Config> Pallet<T> {618 pub fn init_collection(619 owner: T::AccountId,620 data: CreateCollectionData<T::AccountId>,621 ) -> Result<CollectionId, DispatchError> {622 {623 ensure!(624 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,625 Error::<T>::CollectionTokenPrefixLimitExceeded626 );627 }628629 let created_count = <CreatedCollectionCount<T>>::get()630 .0631 .checked_add(1)632 .ok_or(ArithmeticError::Overflow)?;633 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;634 let id = CollectionId(created_count);635636 637 ensure!(638 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,639 <Error<T>>::TotalCollectionsLimitExceeded640 );641642 643644 let collection = Collection {645 owner: owner.clone(),646 name: data.name,647 mode: data.mode.clone(),648 mint_mode: false,649 access: data.access.unwrap_or_default(),650 description: data.description,651 token_prefix: data.token_prefix,652 schema_version: data.schema_version.unwrap_or_default(),653 sponsorship: data654 .pending_sponsor655 .map(SponsorshipState::Unconfirmed)656 .unwrap_or_default(),657 limits: data658 .limits659 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))660 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,661 meta_update_permission: data.meta_update_permission.unwrap_or_default(),662 };663664 let mut collection_properties = up_data_structs::CollectionProperties::get();665 collection_properties666 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))667 .map_err(<Error<T>>::from)?;668669 CollectionProperties::<T>::insert(id, collection_properties);670671 let mut token_props_permissions = PropertiesPermissionMap::new();672 token_props_permissions673 .try_set_from_iter(674 data.token_property_permissions675 .into_iter()676 .map(|property| (property.key, property.permission)),677 )678 .map_err(<Error<T>>::from)?;679680 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);681682 683 {684 let mut imbalance =685 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();686 imbalance.subsume(687 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(688 &T::TreasuryAccountId::get(),689 T::CollectionCreationPrice::get(),690 ),691 );692 <T as Config>::Currency::settle(693 &owner,694 imbalance,695 WithdrawReasons::TRANSFER,696 ExistenceRequirement::KeepAlive,697 )698 .map_err(|_| Error::<T>::NotSufficientFounds)?;699 }700701 <CreatedCollectionCount<T>>::put(created_count);702 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));703 <CollectionById<T>>::insert(id, collection);704 Self::set_field_raw(705 id,706 CollectionField::OffchainSchema,707 data.offchain_schema.into_inner(),708 )709 .expect("data has lower bounds than field");710 Self::set_field_raw(711 id,712 CollectionField::ConstOnChainSchema,713 data.const_on_chain_schema.into_inner(),714 )715 .expect("data has lower bounds than field");716 Ok(id)717 }718719 pub fn destroy_collection(720 collection: CollectionHandle<T>,721 sender: &T::CrossAccountId,722 ) -> DispatchResult {723 ensure!(724 collection.limits.owner_can_destroy(),725 <Error<T>>::NoPermission,726 );727 collection.check_is_owner(sender)?;728729 let destroyed_collections = <DestroyedCollectionCount<T>>::get()730 .0731 .checked_add(1)732 .ok_or(ArithmeticError::Overflow)?;733734 735736 <DestroyedCollectionCount<T>>::put(destroyed_collections);737 <CollectionById<T>>::remove(collection.id);738 <CollectionData<T>>::remove_prefix((collection.id,), None);739 <AdminAmount<T>>::remove(collection.id);740 <IsAdmin<T>>::remove_prefix((collection.id,), None);741 <Allowlist<T>>::remove_prefix((collection.id,), None);742743 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));744 Ok(())745 }746747 pub fn set_collection_property(748 collection: &CollectionHandle<T>,749 sender: &T::CrossAccountId,750 property: Property,751 ) -> DispatchResult {752 collection.check_is_owner_or_admin(sender)?;753754 CollectionProperties::<T>::try_mutate(collection.id, |properties| {755 let property = property.clone();756 properties.try_set(property.key, property.value)757 })758 .map_err(<Error<T>>::from)?;759760 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));761762 Ok(())763 }764765 pub fn set_collection_properties(766 collection: &CollectionHandle<T>,767 sender: &T::CrossAccountId,768 properties: Vec<Property>,769 ) -> DispatchResult {770 for property in properties {771 Self::set_collection_property(collection, sender, property)?;772 }773774 Ok(())775 }776777 pub fn delete_collection_property(778 collection: &CollectionHandle<T>,779 sender: &T::CrossAccountId,780 property_key: PropertyKey,781 ) -> DispatchResult {782 collection.check_is_owner_or_admin(sender)?;783784 CollectionProperties::<T>::try_mutate(collection.id, |properties| {785 properties.remove(&property_key)786 })787 .map_err(<Error<T>>::from)?;788789 Self::deposit_event(Event::CollectionPropertyDeleted(790 collection.id,791 property_key,792 ));793794 Ok(())795 }796797 pub fn delete_collection_properties(798 collection: &CollectionHandle<T>,799 sender: &T::CrossAccountId,800 property_keys: Vec<PropertyKey>,801 ) -> DispatchResult {802 for key in property_keys {803 Self::delete_collection_property(collection, sender, key)?;804 }805806 Ok(())807 }808809 pub fn set_property_permission(810 collection: &CollectionHandle<T>,811 sender: &T::CrossAccountId,812 property_permission: PropertyKeyPermission,813 ) -> DispatchResult {814 collection.check_is_owner_or_admin(sender)?;815816 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);817 let current_permission = all_permissions.get(&property_permission.key);818 if matches![819 current_permission,820 Some(PropertyPermission { mutable: false, .. })821 ] {822 return Err(<Error<T>>::NoPermission.into());823 }824825 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {826 let property_permission = property_permission.clone();827 permissions.try_set(property_permission.key, property_permission.permission)828 })829 .map_err(<Error<T>>::from)?;830831 Self::deposit_event(Event::PropertyPermissionSet(832 collection.id,833 property_permission.key,834 ));835836 Ok(())837 }838839 pub fn set_property_permissions(840 collection: &CollectionHandle<T>,841 sender: &T::CrossAccountId,842 property_permissions: Vec<PropertyKeyPermission>,843 ) -> DispatchResult {844 for prop_pemission in property_permissions {845 Self::set_property_permission(collection, sender, prop_pemission)?;846 }847848 Ok(())849 }850851 pub fn bytes_keys_to_property_keys(852 keys: Vec<Vec<u8>>,853 ) -> Result<Vec<PropertyKey>, DispatchError> {854 keys.into_iter()855 .map(|key| -> Result<PropertyKey, DispatchError> {856 key.try_into()857 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())858 })859 .collect::<Result<Vec<PropertyKey>, DispatchError>>()860 }861862 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {863 let key_str = sp_std::str::from_utf8(key.as_slice())864 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;865866 for ch in key_str.chars() {867 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {868 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());869 }870 }871872 Ok(())873 }874875 pub fn filter_collection_properties(876 collection_id: CollectionId,877 keys: Vec<PropertyKey>,878 ) -> Result<Vec<Property>, DispatchError> {879 let properties = Self::collection_properties(collection_id);880881 let properties = keys882 .into_iter()883 .filter_map(|key| {884 properties.get(&key).map(|value| Property {885 key,886 value: value.clone(),887 })888 })889 .collect();890891 Ok(properties)892 }893894 pub fn filter_property_permissions(895 collection_id: CollectionId,896 keys: Vec<PropertyKey>,897 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {898 let permissions = Self::property_permissions(collection_id);899900 let key_permissions = keys901 .into_iter()902 .filter_map(|key| {903 permissions904 .get(&key)905 .map(|permission| PropertyKeyPermission {906 key,907 permission: permission.clone(),908 })909 })910 .collect();911912 Ok(key_permissions)913 }914915 fn set_field_raw(916 collection_id: CollectionId,917 field: CollectionField,918 value: Vec<u8>,919 ) -> DispatchResult {920 if !value.is_empty() {921 <CollectionData<T>>::insert(922 (collection_id, field),923 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,924 )925 } else {926 <CollectionData<T>>::remove((collection_id, field));927 }928 Ok(())929 }930931 pub fn set_field(932 collection: &CollectionHandle<T>,933 sender: &T::CrossAccountId,934 field: CollectionField,935 value: Vec<u8>,936 ) -> DispatchResult {937 collection.check_is_owner_or_admin(sender)?;938939 940941 Self::set_field_raw(collection.id, field, value)942 }943944 pub fn toggle_allowlist(945 collection: &CollectionHandle<T>,946 sender: &T::CrossAccountId,947 user: &T::CrossAccountId,948 allowed: bool,949 ) -> DispatchResult {950 collection.check_is_owner_or_admin(sender)?;951952 953954 if allowed {955 <Allowlist<T>>::insert((collection.id, user), true);956 } else {957 <Allowlist<T>>::remove((collection.id, user));958 }959960 Ok(())961 }962963 pub fn toggle_admin(964 collection: &CollectionHandle<T>,965 sender: &T::CrossAccountId,966 user: &T::CrossAccountId,967 admin: bool,968 ) -> DispatchResult {969 collection.check_is_owner_or_admin(sender)?;970971 let was_admin = <IsAdmin<T>>::get((collection.id, user));972 if was_admin == admin {973 return Ok(());974 }975 let amount = <AdminAmount<T>>::get(collection.id);976977 if admin {978 let amount = amount979 .checked_add(1)980 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;981 ensure!(982 amount <= Self::collection_admins_limit(),983 <Error<T>>::CollectionAdminCountExceeded,984 );985986 987988 <AdminAmount<T>>::insert(collection.id, amount);989 <IsAdmin<T>>::insert((collection.id, user), true);990 } else {991 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));992 <IsAdmin<T>>::remove((collection.id, user));993 }994995 Ok(())996 }997998 pub fn clamp_limits(999 mode: CollectionMode,1000 old_limit: &CollectionLimits,1001 mut new_limit: CollectionLimits,1002 ) -> Result<CollectionLimits, DispatchError> {1003 macro_rules! limit_default {1004 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1005 $(1006 if let Some($new) = $new.$field {1007 let $old = $old.$field($($arg)?);1008 let _ = $new;1009 let _ = $old;1010 $check1011 } else {1012 $new.$field = $old.$field1013 }1014 )*1015 }};1016 }10171018 limit_default!(old_limit, new_limit,1019 account_token_ownership_limit => ensure!(1020 new_limit <= MAX_TOKEN_OWNERSHIP,1021 <Error<T>>::CollectionLimitBoundsExceeded,1022 ),1023 sponsor_transfer_timeout(match mode {1024 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1025 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1026 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1027 }) => ensure!(1028 new_limit <= MAX_SPONSOR_TIMEOUT,1029 <Error<T>>::CollectionLimitBoundsExceeded,1030 ),1031 sponsored_data_size => ensure!(1032 new_limit <= CUSTOM_DATA_LIMIT,1033 <Error<T>>::CollectionLimitBoundsExceeded,1034 ),1035 token_limit => ensure!(1036 old_limit >= new_limit && new_limit > 0,1037 <Error<T>>::CollectionTokenLimitExceeded1038 ),1039 owner_can_transfer => ensure!(1040 old_limit || !new_limit,1041 <Error<T>>::OwnerPermissionsCantBeReverted,1042 ),1043 owner_can_destroy => ensure!(1044 old_limit || !new_limit,1045 <Error<T>>::OwnerPermissionsCantBeReverted,1046 ),1047 sponsored_data_rate_limit => {},1048 transfers_enabled => {},1049 );1050 Ok(new_limit)1051 }1052}10531054#[macro_export]1055macro_rules! unsupported {1056 () => {1057 Err(<Error<T>>::UnsupportedOperation.into())1058 };1059}106010611062pub trait CommonWeightInfo<CrossAccountId> {1063 fn create_item() -> Weight;1064 fn create_multiple_items(amount: u32) -> Weight;1065 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1066 fn burn_item() -> Weight;1067 fn set_collection_properties(amount: u32) -> Weight;1068 fn delete_collection_properties(amount: u32) -> Weight;1069 fn set_token_properties(amount: u32) -> Weight;1070 fn delete_token_properties(amount: u32) -> Weight;1071 fn set_property_permissions(amount: u32) -> Weight;1072 fn transfer() -> Weight;1073 fn approve() -> Weight;1074 fn transfer_from() -> Weight;1075 fn burn_from() -> Weight;1076 fn set_variable_metadata(bytes: u32) -> Weight;1077}10781079pub trait CommonCollectionOperations<T: Config> {1080 fn create_item(1081 &self,1082 sender: T::CrossAccountId,1083 to: T::CrossAccountId,1084 data: CreateItemData,1085 nesting_budget: &dyn Budget,1086 ) -> DispatchResultWithPostInfo;1087 fn create_multiple_items(1088 &self,1089 sender: T::CrossAccountId,1090 to: T::CrossAccountId,1091 data: Vec<CreateItemData>,1092 nesting_budget: &dyn Budget,1093 ) -> DispatchResultWithPostInfo;1094 fn create_multiple_items_ex(1095 &self,1096 sender: T::CrossAccountId,1097 data: CreateItemExData<T::CrossAccountId>,1098 nesting_budget: &dyn Budget,1099 ) -> DispatchResultWithPostInfo;1100 fn burn_item(1101 &self,1102 sender: T::CrossAccountId,1103 token: TokenId,1104 amount: u128,1105 ) -> DispatchResultWithPostInfo;1106 fn set_collection_properties(1107 &self,1108 sender: T::CrossAccountId,1109 properties: Vec<Property>,1110 ) -> DispatchResultWithPostInfo;1111 fn delete_collection_properties(1112 &self,1113 sender: &T::CrossAccountId,1114 property_keys: Vec<PropertyKey>,1115 ) -> DispatchResultWithPostInfo;1116 fn set_token_properties(1117 &self,1118 sender: T::CrossAccountId,1119 token_id: TokenId,1120 property: Vec<Property>,1121 ) -> DispatchResultWithPostInfo;1122 fn delete_token_properties(1123 &self,1124 sender: T::CrossAccountId,1125 token_id: TokenId,1126 property_keys: Vec<PropertyKey>,1127 ) -> DispatchResultWithPostInfo;1128 fn set_property_permissions(1129 &self,1130 sender: &T::CrossAccountId,1131 property_permissions: Vec<PropertyKeyPermission>,1132 ) -> DispatchResultWithPostInfo;1133 fn transfer(1134 &self,1135 sender: T::CrossAccountId,1136 to: T::CrossAccountId,1137 token: TokenId,1138 amount: u128,1139 nesting_budget: &dyn Budget,1140 ) -> DispatchResultWithPostInfo;1141 fn approve(1142 &self,1143 sender: T::CrossAccountId,1144 spender: T::CrossAccountId,1145 token: TokenId,1146 amount: u128,1147 ) -> DispatchResultWithPostInfo;1148 fn transfer_from(1149 &self,1150 sender: T::CrossAccountId,1151 from: T::CrossAccountId,1152 to: T::CrossAccountId,1153 token: TokenId,1154 amount: u128,1155 nesting_budget: &dyn Budget,1156 ) -> DispatchResultWithPostInfo;1157 fn burn_from(1158 &self,1159 sender: T::CrossAccountId,1160 from: T::CrossAccountId,1161 token: TokenId,1162 amount: u128,1163 nesting_budget: &dyn Budget,1164 ) -> DispatchResultWithPostInfo;11651166 fn set_variable_metadata(1167 &self,1168 sender: T::CrossAccountId,1169 token: TokenId,1170 data: BoundedVec<u8, CustomDataLimit>,1171 ) -> DispatchResultWithPostInfo;11721173 fn check_nesting(1174 &self,1175 sender: T::CrossAccountId,1176 from: (CollectionId, TokenId),1177 under: TokenId,1178 budget: &dyn Budget,1179 ) -> DispatchResult;11801181 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1182 fn collection_tokens(&self) -> Vec<TokenId>;1183 fn token_exists(&self, token: TokenId) -> bool;1184 fn last_token_id(&self) -> TokenId;11851186 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1187 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1188 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1189 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1190 1191 fn total_supply(&self) -> u32;1192 1193 fn account_balance(&self, account: T::CrossAccountId) -> u32;1194 1195 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1196 fn allowance(1197 &self,1198 sender: T::CrossAccountId,1199 spender: T::CrossAccountId,1200 token: TokenId,1201 ) -> u128;1202}120312041205pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1206 let post_info = PostDispatchInfo {1207 actual_weight: Some(weight),1208 pays_fee: Pays::Yes,1209 };1210 match res {1211 Ok(()) => Ok(post_info),1212 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1213 }1214}12151216impl<T: Config> From<PropertiesError> for Error<T> {1217 fn from(error: PropertiesError) -> Self {1218 match error {1219 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1220 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1221 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1222 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1223 }1224 }1225}