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,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29 transactional,30};31use pallet_evm::GasWeightMapping;32use up_data_structs::{33 COLLECTION_NUMBER_LIMIT,34 Collection,35 RpcCollection,36 CollectionId,37 CreateItemData,38 MAX_TOKEN_PREFIX_LENGTH,39 COLLECTION_ADMINS_LIMIT,40 TokenId,41 CollectionStats,42 MAX_TOKEN_OWNERSHIP,43 CollectionMode,44 NFT_SPONSOR_TRANSFER_TIMEOUT,45 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,46 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,47 MAX_SPONSOR_TIMEOUT,48 CUSTOM_DATA_LIMIT,49 CollectionLimits,50 CreateCollectionData,51 SponsorshipState,52 CreateItemExData,53 SponsoringRateLimit,54 budget::Budget,55 COLLECTION_FIELD_LIMIT,56 CollectionField,57 PhantomType,58 Property,59 Properties,60 PropertiesPermissionMap,61 PropertyKey,62 PropertyPermission,63 PropertiesError,64 PropertyKeyPermission,65 TokenData,66 TrySetProperty,67 68 RmrkCollectionInfo,69 RmrkInstanceInfo,70 RmrkResourceInfo,71 RmrkPropertyInfo,72 RmrkBaseInfo,73 RmrkPartType,74 RmrkTheme,75 RmrkNftChild,76};7778pub use pallet::*;79use sp_core::H160;80use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};81#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod dispatch;84pub mod erc;85pub mod eth;8687#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]88pub struct CollectionHandle<T: Config> {89 pub id: CollectionId,90 collection: Collection<T::AccountId>,91 pub recorder: SubstrateRecorder<T>,92}93impl<T: Config> WithRecorder<T> for CollectionHandle<T> {94 fn recorder(&self) -> &SubstrateRecorder<T> {95 &self.recorder96 }97 fn into_recorder(self) -> SubstrateRecorder<T> {98 self.recorder99 }100}101impl<T: Config> CollectionHandle<T> {102 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {103 <CollectionById<T>>::get(id).map(|collection| Self {104 id,105 collection,106 recorder: SubstrateRecorder::new(gas_limit),107 })108 }109 pub fn new(id: CollectionId) -> Option<Self> {110 Self::new_with_gas_limit(id, u64::MAX)111 }112 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {113 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)114 }115 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {116 self.recorder117 .consume_gas(T::GasWeightMapping::weight_to_gas(118 <T as frame_system::Config>::DbWeight::get()119 .read120 .saturating_mul(reads),121 ))122 }123 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {124 self.recorder125 .consume_gas(T::GasWeightMapping::weight_to_gas(126 <T as frame_system::Config>::DbWeight::get()127 .write128 .saturating_mul(writes),129 ))130 }131 pub fn save(self) -> DispatchResult {132 <CollectionById<T>>::insert(self.id, self.collection);133 Ok(())134 }135}136impl<T: Config> Deref for CollectionHandle<T> {137 type Target = Collection<T::AccountId>;138139 fn deref(&self) -> &Self::Target {140 &self.collection141 }142}143144impl<T: Config> DerefMut for CollectionHandle<T> {145 fn deref_mut(&mut self) -> &mut Self::Target {146 &mut self.collection147 }148}149150impl<T: Config> CollectionHandle<T> {151 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {152 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);153 Ok(())154 }155 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {156 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))157 }158 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {159 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);160 Ok(())161 }162 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {163 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)164 }165 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {166 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)167 }168 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {169 ensure!(170 <Allowlist<T>>::get((self.id, user)),171 <Error<T>>::AddressNotInAllowlist172 );173 Ok(())174 }175}176177#[frame_support::pallet]178pub mod pallet {179 use super::*;180 use pallet_evm::account;181 use dispatch::CollectionDispatch;182 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};183 use frame_system::pallet_prelude::*;184 use frame_support::traits::Currency;185 use up_data_structs::{TokenId, mapping::TokenAddressMapping};186 use scale_info::TypeInfo;187188 #[pallet::config]189 pub trait Config:190 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config191 {192 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;193194 type Currency: Currency<Self::AccountId>;195196 #[pallet::constant]197 type CollectionCreationPrice: Get<198 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,199 >;200 type CollectionDispatch: CollectionDispatch<Self>;201202 type TreasuryAccountId: Get<Self::AccountId>;203204 type EvmTokenAddressMapping: TokenAddressMapping<H160>;205 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;206 }207208 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);209210 #[pallet::pallet]211 #[pallet::storage_version(STORAGE_VERSION)]212 #[pallet::generate_store(pub(super) trait Store)]213 pub struct Pallet<T>(_);214215 #[pallet::extra_constants]216 impl<T: Config> Pallet<T> {217 pub fn collection_admins_limit() -> u32 {218 COLLECTION_ADMINS_LIMIT219 }220 }221222 #[pallet::event]223 #[pallet::generate_deposit(pub fn deposit_event)]224 pub enum Event<T: Config> {225 226 227 228 229 230 231 232 233 234 CollectionCreated(CollectionId, u8, T::AccountId),235236 237 238 239 240 241 CollectionDestroyed(CollectionId),242243 244 245 246 247 248 249 250 251 252 253 254 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),255256 257 258 259 260 261 262 263 264 265 266 267 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),268269 270 271 272 273 274 275 276 277 278 279 280 Transfer(281 CollectionId,282 TokenId,283 T::CrossAccountId,284 T::CrossAccountId,285 u128,286 ),287288 289 290 291 292 293 294 295 296 297 Approved(298 CollectionId,299 TokenId,300 T::CrossAccountId,301 T::CrossAccountId,302 u128,303 ),304305 CollectionPropertySet(CollectionId, PropertyKey),306307 CollectionPropertyDeleted(CollectionId, PropertyKey),308309 TokenPropertySet(CollectionId, TokenId, PropertyKey),310311 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),312313 PropertyPermissionSet(CollectionId, PropertyKey),314 }315316 #[pallet::error]317 pub enum Error<T> {318 319 CollectionNotFound,320 321 MustBeTokenOwner,322 323 NoPermission,324 325 PublicMintingNotAllowed,326 327 AddressNotInAllowlist,328329 330 CollectionNameLimitExceeded,331 332 CollectionDescriptionLimitExceeded,333 334 CollectionTokenPrefixLimitExceeded,335 336 TotalCollectionsLimitExceeded,337 338 CollectionAdminCountExceeded,339 340 CollectionLimitBoundsExceeded,341 342 OwnerPermissionsCantBeReverted,343 344 TransferNotAllowed,345 346 AccountTokenLimitExceeded,347 348 CollectionTokenLimitExceeded,349 350 MetadataFlagFrozen,351352 353 TokenNotFound,354 355 TokenValueTooLow,356 357 ApprovedValueTooLow,358 359 CantApproveMoreThanOwned,360361 362 AddressIsZero,363 364 UnsupportedOperation,365366 367 NotSufficientFounds,368369 370 NestingIsDisabled,371 372 OnlyOwnerAllowedToNest,373 374 SourceCollectionIsNotAllowedToNest,375376 377 CollectionFieldSizeExceeded,378379 380 NoSpaceForProperty,381382 383 PropertyLimitReached,384385 386 PropertyKeyIsTooLong,387388 389 InvalidCharacterInPropertyKey,390391 392 EmptyPropertyKey,393 }394395 #[pallet::storage]396 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;397 #[pallet::storage]398 pub type DestroyedCollectionCount<T> =399 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;400401 402 #[pallet::storage]403 pub type CollectionById<T> = StorageMap<404 Hasher = Blake2_128Concat,405 Key = CollectionId,406 Value = Collection<<T as frame_system::Config>::AccountId>,407 QueryKind = OptionQuery,408 >;409410 411 #[pallet::storage]412 #[pallet::getter(fn collection_properties)]413 pub type CollectionProperties<T> = StorageMap<414 Hasher = Blake2_128Concat,415 Key = CollectionId,416 Value = Properties,417 QueryKind = ValueQuery,418 OnEmpty = up_data_structs::CollectionProperties,419 >;420421 #[pallet::storage]422 #[pallet::getter(fn property_permissions)]423 pub type CollectionPropertyPermissions<T> = StorageMap<424 Hasher = Blake2_128Concat,425 Key = CollectionId,426 Value = PropertiesPermissionMap,427 QueryKind = ValueQuery,428 >;429430 431 #[pallet::storage]432 pub type CollectionData<T> = StorageNMap<433 Key = (434 Key<Twox64Concat, CollectionId>,435 Key<Twox64Concat, CollectionField>,436 ),437 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,438 QueryKind = ValueQuery,439 >;440441 #[pallet::storage]442 pub type AdminAmount<T> = StorageMap<443 Hasher = Blake2_128Concat,444 Key = CollectionId,445 Value = u32,446 QueryKind = ValueQuery,447 >;448449 450 #[pallet::storage]451 pub type IsAdmin<T: Config> = StorageNMap<452 Key = (453 Key<Blake2_128Concat, CollectionId>,454 Key<Blake2_128Concat, T::CrossAccountId>,455 ),456 Value = bool,457 QueryKind = ValueQuery,458 >;459460 461 #[pallet::storage]462 pub type Allowlist<T: Config> = StorageNMap<463 Key = (464 Key<Blake2_128Concat, CollectionId>,465 Key<Blake2_128Concat, T::CrossAccountId>,466 ),467 Value = bool,468 QueryKind = ValueQuery,469 >;470471 472 #[pallet::storage]473 pub type DummyStorageValue<T: Config> = StorageValue<474 Value = (475 CollectionStats,476 CollectionId,477 TokenId,478 PhantomType<TokenData<T::CrossAccountId>>,479 PhantomType<RpcCollection<T::AccountId>>,480 481 PhantomType<RmrkCollectionInfo<T::AccountId>>,482 PhantomType<RmrkInstanceInfo<T::AccountId>>,483 PhantomType<RmrkResourceInfo>,484 PhantomType<RmrkPropertyInfo>,485 PhantomType<RmrkBaseInfo<T::AccountId>>,486 PhantomType<RmrkPartType>,487 PhantomType<RmrkTheme>,488 PhantomType<RmrkNftChild>,489 ),490 QueryKind = OptionQuery,491 >;492493 #[pallet::hooks]494 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {495 fn on_runtime_upgrade() -> Weight {496 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {497 use up_data_structs::{CollectionVersion1, CollectionVersion2};498 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {499 Self::set_field_raw(500 id,501 CollectionField::OffchainSchema,502 v.offchain_schema.clone().into_inner(),503 )504 .expect("data has lower bounds than field");505 Self::set_field_raw(506 id,507 CollectionField::ConstOnChainSchema,508 v.const_on_chain_schema.clone().into_inner(),509 )510 .expect("data has lower bounds than field");511512 Some(CollectionVersion2::from(v))513 });514 }515516 0517 }518 }519}520521impl<T: Config> Pallet<T> {522 523 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {524 ensure!(525 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,526 <Error<T>>::AddressIsZero527 );528 Ok(())529 }530 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {531 <IsAdmin<T>>::iter_prefix((collection,))532 .map(|(a, _)| a)533 .collect()534 }535 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {536 <Allowlist<T>>::iter_prefix((collection,))537 .map(|(a, _)| a)538 .collect()539 }540 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {541 <Allowlist<T>>::get((collection, user))542 }543 pub fn collection_stats() -> CollectionStats {544 let created = <CreatedCollectionCount<T>>::get();545 let destroyed = <DestroyedCollectionCount<T>>::get();546 CollectionStats {547 created: created.0,548 destroyed: destroyed.0,549 alive: created.0 - destroyed.0,550 }551 }552553 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {554 let collection = <CollectionById<T>>::get(collection);555 if collection.is_none() {556 return None;557 }558559 let collection = collection.unwrap();560 let limits = collection.limits;561 let effective_limits = CollectionLimits {562 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),563 sponsored_data_size: Some(limits.sponsored_data_size()),564 sponsored_data_rate_limit: Some(565 limits566 .sponsored_data_rate_limit567 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),568 ),569 token_limit: Some(limits.token_limit()),570 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(571 match collection.mode {572 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,573 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,574 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,575 },576 )),577 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),578 owner_can_transfer: Some(limits.owner_can_transfer()),579 owner_can_destroy: Some(limits.owner_can_destroy()),580 transfers_enabled: Some(limits.transfers_enabled()),581 nesting_rule: Some(limits.nesting_rule().clone()),582 };583584 Some(effective_limits)585 }586587 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {588 let Collection {589 name,590 description,591 owner,592 mode,593 access,594 token_prefix,595 mint_mode,596 schema_version,597 sponsorship,598 limits,599 } = <CollectionById<T>>::get(collection)?;600601 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)602 .iter()603 .map(|(key, permission)| PropertyKeyPermission {604 key: key.clone(),605 permission: permission.clone(),606 })607 .collect();608609 let properties = <CollectionProperties<T>>::get(collection)610 .iter()611 .map(|(key, value)| Property {612 key: key.clone(),613 value: value.clone(),614 })615 .collect();616617 Some(RpcCollection {618 name: name.into_inner(),619 description: description.into_inner(),620 owner,621 mode,622 access,623 token_prefix: token_prefix.into_inner(),624 mint_mode,625 schema_version,626 sponsorship,627 limits,628 offchain_schema: <CollectionData<T>>::get((629 collection,630 CollectionField::OffchainSchema,631 ))632 .into_inner(),633 const_on_chain_schema: <CollectionData<T>>::get((634 collection,635 CollectionField::ConstOnChainSchema,636 ))637 .into_inner(),638 token_property_permissions,639 properties,640 })641 }642}643644impl<T: Config> Pallet<T> {645 pub fn init_collection(646 owner: T::AccountId,647 data: CreateCollectionData<T::AccountId>,648 ) -> Result<CollectionId, DispatchError> {649 {650 ensure!(651 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,652 Error::<T>::CollectionTokenPrefixLimitExceeded653 );654 }655656 let created_count = <CreatedCollectionCount<T>>::get()657 .0658 .checked_add(1)659 .ok_or(ArithmeticError::Overflow)?;660 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;661 let id = CollectionId(created_count);662663 664 ensure!(665 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,666 <Error<T>>::TotalCollectionsLimitExceeded667 );668669 670671 let collection = Collection {672 owner: owner.clone(),673 name: data.name,674 mode: data.mode.clone(),675 mint_mode: false,676 access: data.access.unwrap_or_default(),677 description: data.description,678 token_prefix: data.token_prefix,679 schema_version: data.schema_version.unwrap_or_default(),680 sponsorship: data681 .pending_sponsor682 .map(SponsorshipState::Unconfirmed)683 .unwrap_or_default(),684 limits: data685 .limits686 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))687 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,688 };689690 let mut collection_properties = up_data_structs::CollectionProperties::get();691 collection_properties692 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))693 .map_err(<Error<T>>::from)?;694695 CollectionProperties::<T>::insert(id, collection_properties);696697 let mut token_props_permissions = PropertiesPermissionMap::new();698 token_props_permissions699 .try_set_from_iter(700 data.token_property_permissions701 .into_iter()702 .map(|property| (property.key, property.permission)),703 )704 .map_err(<Error<T>>::from)?;705706 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);707708 709 {710 let mut imbalance =711 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();712 imbalance.subsume(713 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(714 &T::TreasuryAccountId::get(),715 T::CollectionCreationPrice::get(),716 ),717 );718 <T as Config>::Currency::settle(719 &owner,720 imbalance,721 WithdrawReasons::TRANSFER,722 ExistenceRequirement::KeepAlive,723 )724 .map_err(|_| Error::<T>::NotSufficientFounds)?;725 }726727 <CreatedCollectionCount<T>>::put(created_count);728 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));729 <CollectionById<T>>::insert(id, collection);730 Self::set_field_raw(731 id,732 CollectionField::OffchainSchema,733 data.offchain_schema.into_inner(),734 )735 .expect("data has lower bounds than field");736 Self::set_field_raw(737 id,738 CollectionField::ConstOnChainSchema,739 data.const_on_chain_schema.into_inner(),740 )741 .expect("data has lower bounds than field");742 Ok(id)743 }744745 pub fn destroy_collection(746 collection: CollectionHandle<T>,747 sender: &T::CrossAccountId,748 ) -> DispatchResult {749 ensure!(750 collection.limits.owner_can_destroy(),751 <Error<T>>::NoPermission,752 );753 collection.check_is_owner(sender)?;754755 let destroyed_collections = <DestroyedCollectionCount<T>>::get()756 .0757 .checked_add(1)758 .ok_or(ArithmeticError::Overflow)?;759760 761762 <DestroyedCollectionCount<T>>::put(destroyed_collections);763 <CollectionById<T>>::remove(collection.id);764 <CollectionData<T>>::remove_prefix((collection.id,), None);765 <AdminAmount<T>>::remove(collection.id);766 <IsAdmin<T>>::remove_prefix((collection.id,), None);767 <Allowlist<T>>::remove_prefix((collection.id,), None);768769 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));770 Ok(())771 }772773 pub fn set_collection_property(774 collection: &CollectionHandle<T>,775 sender: &T::CrossAccountId,776 property: Property,777 ) -> DispatchResult {778 collection.check_is_owner_or_admin(sender)?;779780 CollectionProperties::<T>::try_mutate(collection.id, |properties| {781 let property = property.clone();782 properties.try_set(property.key, property.value)783 })784 .map_err(<Error<T>>::from)?;785786 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));787788 Ok(())789 }790791 #[transactional]792 pub fn set_collection_properties(793 collection: &CollectionHandle<T>,794 sender: &T::CrossAccountId,795 properties: Vec<Property>,796 ) -> DispatchResult {797 for property in properties {798 Self::set_collection_property(collection, sender, property)?;799 }800801 Ok(())802 }803804 pub fn delete_collection_property(805 collection: &CollectionHandle<T>,806 sender: &T::CrossAccountId,807 property_key: PropertyKey,808 ) -> DispatchResult {809 collection.check_is_owner_or_admin(sender)?;810811 CollectionProperties::<T>::try_mutate(collection.id, |properties| {812 properties.remove(&property_key)813 })814 .map_err(<Error<T>>::from)?;815816 Self::deposit_event(Event::CollectionPropertyDeleted(817 collection.id,818 property_key,819 ));820821 Ok(())822 }823824 #[transactional]825 pub fn delete_collection_properties(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 property_keys: Vec<PropertyKey>,829 ) -> DispatchResult {830 for key in property_keys {831 Self::delete_collection_property(collection, sender, key)?;832 }833834 Ok(())835 }836837 pub fn set_property_permission(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_permission: PropertyKeyPermission,841 ) -> DispatchResult {842 collection.check_is_owner_or_admin(sender)?;843844 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);845 let current_permission = all_permissions.get(&property_permission.key);846 if matches![847 current_permission,848 Some(PropertyPermission { mutable: false, .. })849 ] {850 return Err(<Error<T>>::NoPermission.into());851 }852853 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {854 let property_permission = property_permission.clone();855 permissions.try_set(property_permission.key, property_permission.permission)856 })857 .map_err(<Error<T>>::from)?;858859 Self::deposit_event(Event::PropertyPermissionSet(860 collection.id,861 property_permission.key,862 ));863864 Ok(())865 }866867 #[transactional]868 pub fn set_property_permissions(869 collection: &CollectionHandle<T>,870 sender: &T::CrossAccountId,871 property_permissions: Vec<PropertyKeyPermission>,872 ) -> DispatchResult {873 for prop_pemission in property_permissions {874 Self::set_property_permission(collection, sender, prop_pemission)?;875 }876877 Ok(())878 }879880 pub fn bytes_keys_to_property_keys(881 keys: Vec<Vec<u8>>,882 ) -> Result<Vec<PropertyKey>, DispatchError> {883 keys.into_iter()884 .map(|key| -> Result<PropertyKey, DispatchError> {885 key.try_into()886 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())887 })888 .collect::<Result<Vec<PropertyKey>, DispatchError>>()889 }890891 pub fn filter_collection_properties(892 collection_id: CollectionId,893 keys: Option<Vec<PropertyKey>>,894 ) -> Result<Vec<Property>, DispatchError> {895 let properties = Self::collection_properties(collection_id);896897 let properties = keys898 .map(|keys| {899 keys.into_iter()900 .filter_map(|key| {901 properties.get(&key).map(|value| Property {902 key,903 value: value.clone(),904 })905 })906 .collect()907 })908 .unwrap_or_else(|| {909 properties910 .iter()911 .map(|(key, value)| Property {912 key: key.clone(),913 value: value.clone(),914 })915 .collect()916 });917918 Ok(properties)919 }920921 pub fn filter_property_permissions(922 collection_id: CollectionId,923 keys: Option<Vec<PropertyKey>>,924 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {925 let permissions = Self::property_permissions(collection_id);926927 let key_permissions = keys928 .map(|keys| {929 keys.into_iter()930 .filter_map(|key| {931 permissions932 .get(&key)933 .map(|permission| PropertyKeyPermission {934 key,935 permission: permission.clone(),936 })937 })938 .collect()939 })940 .unwrap_or_else(|| {941 permissions942 .iter()943 .map(|(key, permission)| PropertyKeyPermission {944 key: key.clone(),945 permission: permission.clone(),946 })947 .collect()948 });949950 Ok(key_permissions)951 }952953 fn set_field_raw(954 collection_id: CollectionId,955 field: CollectionField,956 value: Vec<u8>,957 ) -> DispatchResult {958 if !value.is_empty() {959 <CollectionData<T>>::insert(960 (collection_id, field),961 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,962 )963 } else {964 <CollectionData<T>>::remove((collection_id, field));965 }966 Ok(())967 }968969 pub fn set_field(970 collection: &CollectionHandle<T>,971 sender: &T::CrossAccountId,972 field: CollectionField,973 value: Vec<u8>,974 ) -> DispatchResult {975 collection.check_is_owner_or_admin(sender)?;976977 978979 Self::set_field_raw(collection.id, field, value)980 }981982 pub fn toggle_allowlist(983 collection: &CollectionHandle<T>,984 sender: &T::CrossAccountId,985 user: &T::CrossAccountId,986 allowed: bool,987 ) -> DispatchResult {988 collection.check_is_owner_or_admin(sender)?;989990 991992 if allowed {993 <Allowlist<T>>::insert((collection.id, user), true);994 } else {995 <Allowlist<T>>::remove((collection.id, user));996 }997998 Ok(())999 }10001001 pub fn toggle_admin(1002 collection: &CollectionHandle<T>,1003 sender: &T::CrossAccountId,1004 user: &T::CrossAccountId,1005 admin: bool,1006 ) -> DispatchResult {1007 collection.check_is_owner_or_admin(sender)?;10081009 let was_admin = <IsAdmin<T>>::get((collection.id, user));1010 if was_admin == admin {1011 return Ok(());1012 }1013 let amount = <AdminAmount<T>>::get(collection.id);10141015 if admin {1016 let amount = amount1017 .checked_add(1)1018 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1019 ensure!(1020 amount <= Self::collection_admins_limit(),1021 <Error<T>>::CollectionAdminCountExceeded,1022 );10231024 10251026 <AdminAmount<T>>::insert(collection.id, amount);1027 <IsAdmin<T>>::insert((collection.id, user), true);1028 } else {1029 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1030 <IsAdmin<T>>::remove((collection.id, user));1031 }10321033 Ok(())1034 }10351036 pub fn clamp_limits(1037 mode: CollectionMode,1038 old_limit: &CollectionLimits,1039 mut new_limit: CollectionLimits,1040 ) -> Result<CollectionLimits, DispatchError> {1041 macro_rules! limit_default {1042 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1043 $(1044 if let Some($new) = $new.$field {1045 let $old = $old.$field($($arg)?);1046 let _ = $new;1047 let _ = $old;1048 $check1049 } else {1050 $new.$field = $old.$field1051 }1052 )*1053 }};1054 }10551056 limit_default!(old_limit, new_limit,1057 account_token_ownership_limit => ensure!(1058 new_limit <= MAX_TOKEN_OWNERSHIP,1059 <Error<T>>::CollectionLimitBoundsExceeded,1060 ),1061 sponsor_transfer_timeout(match mode {1062 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1063 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1064 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1065 }) => ensure!(1066 new_limit <= MAX_SPONSOR_TIMEOUT,1067 <Error<T>>::CollectionLimitBoundsExceeded,1068 ),1069 sponsored_data_size => ensure!(1070 new_limit <= CUSTOM_DATA_LIMIT,1071 <Error<T>>::CollectionLimitBoundsExceeded,1072 ),1073 token_limit => ensure!(1074 old_limit >= new_limit && new_limit > 0,1075 <Error<T>>::CollectionTokenLimitExceeded1076 ),1077 owner_can_transfer => ensure!(1078 old_limit || !new_limit,1079 <Error<T>>::OwnerPermissionsCantBeReverted,1080 ),1081 owner_can_destroy => ensure!(1082 old_limit || !new_limit,1083 <Error<T>>::OwnerPermissionsCantBeReverted,1084 ),1085 sponsored_data_rate_limit => {},1086 transfers_enabled => {},1087 );1088 Ok(new_limit)1089 }1090}10911092#[macro_export]1093macro_rules! unsupported {1094 () => {1095 Err(<Error<T>>::UnsupportedOperation.into())1096 };1097}109810991100pub trait CommonWeightInfo<CrossAccountId> {1101 fn create_item() -> Weight;1102 fn create_multiple_items(amount: u32) -> Weight;1103 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1104 fn burn_item() -> Weight;1105 fn set_collection_properties(amount: u32) -> Weight;1106 fn delete_collection_properties(amount: u32) -> Weight;1107 fn set_token_properties(amount: u32) -> Weight;1108 fn delete_token_properties(amount: u32) -> Weight;1109 fn set_property_permissions(amount: u32) -> Weight;1110 fn transfer() -> Weight;1111 fn approve() -> Weight;1112 fn transfer_from() -> Weight;1113 fn burn_from() -> Weight;1114}11151116pub trait CommonCollectionOperations<T: Config> {1117 fn create_item(1118 &self,1119 sender: T::CrossAccountId,1120 to: T::CrossAccountId,1121 data: CreateItemData,1122 nesting_budget: &dyn Budget,1123 ) -> DispatchResultWithPostInfo;1124 fn create_multiple_items(1125 &self,1126 sender: T::CrossAccountId,1127 to: T::CrossAccountId,1128 data: Vec<CreateItemData>,1129 nesting_budget: &dyn Budget,1130 ) -> DispatchResultWithPostInfo;1131 fn create_multiple_items_ex(1132 &self,1133 sender: T::CrossAccountId,1134 data: CreateItemExData<T::CrossAccountId>,1135 nesting_budget: &dyn Budget,1136 ) -> DispatchResultWithPostInfo;1137 fn burn_item(1138 &self,1139 sender: T::CrossAccountId,1140 token: TokenId,1141 amount: u128,1142 ) -> DispatchResultWithPostInfo;1143 fn set_collection_properties(1144 &self,1145 sender: T::CrossAccountId,1146 properties: Vec<Property>,1147 ) -> DispatchResultWithPostInfo;1148 fn delete_collection_properties(1149 &self,1150 sender: &T::CrossAccountId,1151 property_keys: Vec<PropertyKey>,1152 ) -> DispatchResultWithPostInfo;1153 fn set_token_properties(1154 &self,1155 sender: T::CrossAccountId,1156 token_id: TokenId,1157 property: Vec<Property>,1158 ) -> DispatchResultWithPostInfo;1159 fn delete_token_properties(1160 &self,1161 sender: T::CrossAccountId,1162 token_id: TokenId,1163 property_keys: Vec<PropertyKey>,1164 ) -> DispatchResultWithPostInfo;1165 fn set_property_permissions(1166 &self,1167 sender: &T::CrossAccountId,1168 property_permissions: Vec<PropertyKeyPermission>,1169 ) -> DispatchResultWithPostInfo;1170 fn transfer(1171 &self,1172 sender: T::CrossAccountId,1173 to: T::CrossAccountId,1174 token: TokenId,1175 amount: u128,1176 nesting_budget: &dyn Budget,1177 ) -> DispatchResultWithPostInfo;1178 fn approve(1179 &self,1180 sender: T::CrossAccountId,1181 spender: T::CrossAccountId,1182 token: TokenId,1183 amount: u128,1184 ) -> DispatchResultWithPostInfo;1185 fn transfer_from(1186 &self,1187 sender: T::CrossAccountId,1188 from: T::CrossAccountId,1189 to: T::CrossAccountId,1190 token: TokenId,1191 amount: u128,1192 nesting_budget: &dyn Budget,1193 ) -> DispatchResultWithPostInfo;1194 fn burn_from(1195 &self,1196 sender: T::CrossAccountId,1197 from: T::CrossAccountId,1198 token: TokenId,1199 amount: u128,1200 nesting_budget: &dyn Budget,1201 ) -> DispatchResultWithPostInfo;12021203 fn check_nesting(1204 &self,1205 sender: T::CrossAccountId,1206 from: (CollectionId, TokenId),1207 under: TokenId,1208 budget: &dyn Budget,1209 ) -> DispatchResult;12101211 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1212 fn collection_tokens(&self) -> Vec<TokenId>;1213 fn token_exists(&self, token: TokenId) -> bool;1214 fn last_token_id(&self) -> TokenId;12151216 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1217 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1218 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1219 1220 fn total_supply(&self) -> u32;1221 1222 fn account_balance(&self, account: T::CrossAccountId) -> u32;1223 1224 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1225 fn allowance(1226 &self,1227 sender: T::CrossAccountId,1228 spender: T::CrossAccountId,1229 token: TokenId,1230 ) -> u128;1231}123212331234pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1235 let post_info = PostDispatchInfo {1236 actual_weight: Some(weight),1237 pays_fee: Pays::Yes,1238 };1239 match res {1240 Ok(()) => Ok(post_info),1241 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1242 }1243}12441245impl<T: Config> From<PropertiesError> for Error<T> {1246 fn from(error: PropertiesError) -> Self {1247 match error {1248 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1249 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1250 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1251 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1252 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1253 }1254 }1255}