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(eth::collection_id_to_address(id), 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 log_mirrored(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_mirrored(log)80 }81 pub fn log_direct(&self, log: impl evm_coder::ToLog) {82 self.recorder.log_direct(log)83 }84 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {85 self.recorder86 .consume_gas(T::GasWeightMapping::weight_to_gas(87 <T as frame_system::Config>::DbWeight::get()88 .read89 .saturating_mul(reads),90 ))91 }92 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {93 self.recorder94 .consume_gas(T::GasWeightMapping::weight_to_gas(95 <T as frame_system::Config>::DbWeight::get()96 .write97 .saturating_mul(writes),98 ))99 }100 pub fn submit_logs(self) {101 self.recorder.submit_logs()102 }103 pub fn save(self) -> DispatchResult {104 self.recorder.submit_logs();105 <CollectionById<T>>::insert(self.id, self.collection);106 Ok(())107 }108}109impl<T: Config> Deref for CollectionHandle<T> {110 type Target = Collection<T::AccountId>;111112 fn deref(&self) -> &Self::Target {113 &self.collection114 }115}116117impl<T: Config> DerefMut for CollectionHandle<T> {118 fn deref_mut(&mut self) -> &mut Self::Target {119 &mut self.collection120 }121}122123impl<T: Config> CollectionHandle<T> {124 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {125 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);126 Ok(())127 }128 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {129 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))130 }131 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {132 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);133 Ok(())134 }135 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {139 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)140 }141 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {142 ensure!(143 <Allowlist<T>>::get((self.id, user)),144 <Error<T>>::AddressNotInAllowlist145 );146 Ok(())147 }148149 pub fn check_can_update_meta(150 &self,151 subject: &T::CrossAccountId,152 item_owner: &T::CrossAccountId,153 ) -> DispatchResult {154 match self.meta_update_permission {155 MetaUpdatePermission::ItemOwner => {156 ensure!(subject == item_owner, <Error<T>>::NoPermission);157 Ok(())158 }159 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),160 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),161 }162 }163}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use pallet_evm::account;169 use dispatch::CollectionDispatch;170 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};171 use frame_system::pallet_prelude::*;172 use frame_support::traits::Currency;173 use up_data_structs::{TokenId, mapping::TokenAddressMapping};174 use scale_info::TypeInfo;175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config179 {180 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;181182 type Currency: Currency<Self::AccountId>;183184 #[pallet::constant]185 type CollectionCreationPrice: Get<186 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,187 >;188 type CollectionDispatch: CollectionDispatch<Self>;189190 type TreasuryAccountId: Get<Self::AccountId>;191192 type EvmTokenAddressMapping: TokenAddressMapping<H160>;193 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;194 }195196 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);197198 #[pallet::pallet]199 #[pallet::storage_version(STORAGE_VERSION)]200 #[pallet::generate_store(pub(super) trait Store)]201 pub struct Pallet<T>(_);202203 #[pallet::extra_constants]204 impl<T: Config> Pallet<T> {205 pub fn collection_admins_limit() -> u32 {206 COLLECTION_ADMINS_LIMIT207 }208 }209210 #[pallet::event]211 #[pallet::generate_deposit(pub fn deposit_event)]212 pub enum Event<T: Config> {213 214 215 216 217 218 219 220 221 222 CollectionCreated(CollectionId, u8, T::AccountId),223224 225 226 227 228 229 CollectionDestroyed(CollectionId),230231 232 233 234 235 236 237 238 239 240 241 242 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 Transfer(269 CollectionId,270 TokenId,271 T::CrossAccountId,272 T::CrossAccountId,273 u128,274 ),275276 277 278 279 280 281 282 283 284 285 Approved(286 CollectionId,287 TokenId,288 T::CrossAccountId,289 T::CrossAccountId,290 u128,291 ),292293 CollectionPropertySet(CollectionId, PropertyKey),294295 CollectionPropertyDeleted(CollectionId, PropertyKey),296297 TokenPropertySet(CollectionId, TokenId, PropertyKey),298299 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),300301 PropertyPermissionSet(CollectionId, PropertyKey),302 }303304 #[pallet::error]305 pub enum Error<T> {306 307 CollectionNotFound,308 309 MustBeTokenOwner,310 311 NoPermission,312 313 PublicMintingNotAllowed,314 315 AddressNotInAllowlist,316317 318 CollectionNameLimitExceeded,319 320 CollectionDescriptionLimitExceeded,321 322 CollectionTokenPrefixLimitExceeded,323 324 TotalCollectionsLimitExceeded,325 326 TokenVariableDataLimitExceeded,327 328 CollectionAdminCountExceeded,329 330 CollectionLimitBoundsExceeded,331 332 OwnerPermissionsCantBeReverted,333 334 TransferNotAllowed,335 336 AccountTokenLimitExceeded,337 338 CollectionTokenLimitExceeded,339 340 MetadataFlagFrozen,341342 343 TokenNotFound,344 345 TokenValueTooLow,346 347 ApprovedValueTooLow,348 349 CantApproveMoreThanOwned,350351 352 AddressIsZero,353 354 UnsupportedOperation,355356 357 NotSufficientFounds,358359 360 NestingIsDisabled,361 362 OnlyOwnerAllowedToNest,363 364 SourceCollectionIsNotAllowedToNest,365366 367 CollectionFieldSizeExceeded,368369 370 NoSpaceForProperty,371372 373 PropertyLimitReached,374375 376 UnableToReadUnboundedKeys,377378 379 InvalidCharacterInPropertyKey,380381 382 EmptyPropertyKey,383 }384385 #[pallet::storage]386 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;387 #[pallet::storage]388 pub type DestroyedCollectionCount<T> =389 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;390391 392 #[pallet::storage]393 pub type CollectionById<T> = StorageMap<394 Hasher = Blake2_128Concat,395 Key = CollectionId,396 Value = Collection<<T as frame_system::Config>::AccountId>,397 QueryKind = OptionQuery,398 >;399400 401 #[pallet::storage]402 #[pallet::getter(fn collection_properties)]403 pub type CollectionProperties<T> = StorageMap<404 Hasher = Blake2_128Concat,405 Key = CollectionId,406 Value = Properties,407 QueryKind = ValueQuery,408 OnEmpty = up_data_structs::CollectionProperties,409 >;410411 #[pallet::storage]412 #[pallet::getter(fn property_permissions)]413 pub type CollectionPropertyPermissions<T> = StorageMap<414 Hasher = Blake2_128Concat,415 Key = CollectionId,416 Value = PropertiesPermissionMap,417 QueryKind = ValueQuery,418 >;419420 421 #[pallet::storage]422 pub type CollectionData<T> = StorageNMap<423 Key = (424 Key<Twox64Concat, CollectionId>,425 Key<Twox64Concat, CollectionField>,426 ),427 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,428 QueryKind = ValueQuery,429 >;430431 #[pallet::storage]432 pub type AdminAmount<T> = StorageMap<433 Hasher = Blake2_128Concat,434 Key = CollectionId,435 Value = u32,436 QueryKind = ValueQuery,437 >;438439 440 #[pallet::storage]441 pub type IsAdmin<T: Config> = StorageNMap<442 Key = (443 Key<Blake2_128Concat, CollectionId>,444 Key<Blake2_128Concat, T::CrossAccountId>,445 ),446 Value = bool,447 QueryKind = ValueQuery,448 >;449450 451 #[pallet::storage]452 pub type Allowlist<T: Config> = StorageNMap<453 Key = (454 Key<Blake2_128Concat, CollectionId>,455 Key<Blake2_128Concat, T::CrossAccountId>,456 ),457 Value = bool,458 QueryKind = ValueQuery,459 >;460461 462 #[pallet::storage]463 pub type DummyStorageValue<T: Config> = StorageValue<464 Value = (465 CollectionStats,466 CollectionId,467 TokenId,468 PhantomType<TokenData<T::CrossAccountId>>,469 PhantomType<RpcCollection<T::AccountId>>,470 ),471 QueryKind = OptionQuery,472 >;473474 #[pallet::hooks]475 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {476 fn on_runtime_upgrade() -> Weight {477 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {478 use up_data_structs::{CollectionVersion1, CollectionVersion2};479 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {480 Self::set_field_raw(481 id,482 CollectionField::OffchainSchema,483 v.offchain_schema.clone().into_inner(),484 )485 .expect("data has lower bounds than field");486 Self::set_field_raw(487 id,488 CollectionField::ConstOnChainSchema,489 v.const_on_chain_schema.clone().into_inner(),490 )491 .expect("data has lower bounds than field");492493 Some(CollectionVersion2::from(v))494 });495 }496497 0498 }499 }500}501502impl<T: Config> Pallet<T> {503 504 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {505 ensure!(506 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,507 <Error<T>>::AddressIsZero508 );509 Ok(())510 }511 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {512 <IsAdmin<T>>::iter_prefix((collection,))513 .map(|(a, _)| a)514 .collect()515 }516 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {517 <Allowlist<T>>::iter_prefix((collection,))518 .map(|(a, _)| a)519 .collect()520 }521 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {522 <Allowlist<T>>::get((collection, user))523 }524 pub fn collection_stats() -> CollectionStats {525 let created = <CreatedCollectionCount<T>>::get();526 let destroyed = <DestroyedCollectionCount<T>>::get();527 CollectionStats {528 created: created.0,529 destroyed: destroyed.0,530 alive: created.0 - destroyed.0,531 }532 }533534 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {535 let collection = <CollectionById<T>>::get(collection);536 if collection.is_none() {537 return None;538 }539540 let collection = collection.unwrap();541 let limits = collection.limits;542 let effective_limits = CollectionLimits {543 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),544 sponsored_data_size: Some(limits.sponsored_data_size()),545 sponsored_data_rate_limit: Some(546 limits547 .sponsored_data_rate_limit548 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),549 ),550 token_limit: Some(limits.token_limit()),551 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(552 match collection.mode {553 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,554 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,555 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,556 },557 )),558 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),559 owner_can_transfer: Some(limits.owner_can_transfer()),560 owner_can_destroy: Some(limits.owner_can_destroy()),561 transfers_enabled: Some(limits.transfers_enabled()),562 nesting_rule: Some(limits.nesting_rule().clone()),563 };564565 Some(effective_limits)566 }567568 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {569 let Collection {570 name,571 description,572 owner,573 mode,574 access,575 token_prefix,576 mint_mode,577 schema_version,578 sponsorship,579 limits,580 meta_update_permission,581 } = <CollectionById<T>>::get(collection)?;582583 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)584 .iter()585 .map(|(key, permission)| PropertyKeyPermission {586 key: key.clone(),587 permission: permission.clone(),588 })589 .collect();590591 let properties = <CollectionProperties<T>>::get(collection)592 .iter()593 .map(|(key, value)| Property {594 key: key.clone(),595 value: value.clone(),596 })597 .collect();598599 Some(RpcCollection {600 name: name.into_inner(),601 description: description.into_inner(),602 owner,603 mode,604 access,605 token_prefix: token_prefix.into_inner(),606 mint_mode,607 schema_version,608 sponsorship,609 limits,610 meta_update_permission,611 offchain_schema: <CollectionData<T>>::get((612 collection,613 CollectionField::OffchainSchema,614 ))615 .into_inner(),616 const_on_chain_schema: <CollectionData<T>>::get((617 collection,618 CollectionField::ConstOnChainSchema,619 ))620 .into_inner(),621 token_property_permissions,622 properties,623 })624 }625}626627impl<T: Config> Pallet<T> {628 pub fn init_collection(629 owner: T::AccountId,630 data: CreateCollectionData<T::AccountId>,631 ) -> Result<CollectionId, DispatchError> {632 {633 ensure!(634 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,635 Error::<T>::CollectionTokenPrefixLimitExceeded636 );637 }638639 let created_count = <CreatedCollectionCount<T>>::get()640 .0641 .checked_add(1)642 .ok_or(ArithmeticError::Overflow)?;643 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;644 let id = CollectionId(created_count);645646 647 ensure!(648 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,649 <Error<T>>::TotalCollectionsLimitExceeded650 );651652 653654 let collection = Collection {655 owner: owner.clone(),656 name: data.name,657 mode: data.mode.clone(),658 mint_mode: false,659 access: data.access.unwrap_or_default(),660 description: data.description,661 token_prefix: data.token_prefix,662 schema_version: data.schema_version.unwrap_or_default(),663 sponsorship: data664 .pending_sponsor665 .map(SponsorshipState::Unconfirmed)666 .unwrap_or_default(),667 limits: data668 .limits669 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))670 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,671 meta_update_permission: data.meta_update_permission.unwrap_or_default(),672 };673674 let mut collection_properties = up_data_structs::CollectionProperties::get();675 collection_properties676 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))677 .map_err(<Error<T>>::from)?;678679 CollectionProperties::<T>::insert(id, collection_properties);680681 let mut token_props_permissions = PropertiesPermissionMap::new();682 token_props_permissions683 .try_set_from_iter(684 data.token_property_permissions685 .into_iter()686 .map(|property| (property.key, property.permission)),687 )688 .map_err(<Error<T>>::from)?;689690 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);691692 693 {694 let mut imbalance =695 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();696 imbalance.subsume(697 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(698 &T::TreasuryAccountId::get(),699 T::CollectionCreationPrice::get(),700 ),701 );702 <T as Config>::Currency::settle(703 &owner,704 imbalance,705 WithdrawReasons::TRANSFER,706 ExistenceRequirement::KeepAlive,707 )708 .map_err(|_| Error::<T>::NotSufficientFounds)?;709 }710711 <CreatedCollectionCount<T>>::put(created_count);712 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));713 <CollectionById<T>>::insert(id, collection);714 Self::set_field_raw(715 id,716 CollectionField::OffchainSchema,717 data.offchain_schema.into_inner(),718 )719 .expect("data has lower bounds than field");720 Self::set_field_raw(721 id,722 CollectionField::ConstOnChainSchema,723 data.const_on_chain_schema.into_inner(),724 )725 .expect("data has lower bounds than field");726 Ok(id)727 }728729 pub fn destroy_collection(730 collection: CollectionHandle<T>,731 sender: &T::CrossAccountId,732 ) -> DispatchResult {733 ensure!(734 collection.limits.owner_can_destroy(),735 <Error<T>>::NoPermission,736 );737 collection.check_is_owner(sender)?;738739 let destroyed_collections = <DestroyedCollectionCount<T>>::get()740 .0741 .checked_add(1)742 .ok_or(ArithmeticError::Overflow)?;743744 745746 <DestroyedCollectionCount<T>>::put(destroyed_collections);747 <CollectionById<T>>::remove(collection.id);748 <CollectionData<T>>::remove_prefix((collection.id,), None);749 <AdminAmount<T>>::remove(collection.id);750 <IsAdmin<T>>::remove_prefix((collection.id,), None);751 <Allowlist<T>>::remove_prefix((collection.id,), None);752753 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));754 Ok(())755 }756757 pub fn set_collection_property(758 collection: &CollectionHandle<T>,759 sender: &T::CrossAccountId,760 property: Property,761 ) -> DispatchResult {762 collection.check_is_owner_or_admin(sender)?;763764 CollectionProperties::<T>::try_mutate(collection.id, |properties| {765 let property = property.clone();766 properties.try_set(property.key, property.value)767 })768 .map_err(<Error<T>>::from)?;769770 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));771772 Ok(())773 }774775 pub fn set_collection_properties(776 collection: &CollectionHandle<T>,777 sender: &T::CrossAccountId,778 properties: Vec<Property>,779 ) -> DispatchResult {780 for property in properties {781 Self::set_collection_property(collection, sender, property)?;782 }783784 Ok(())785 }786787 pub fn delete_collection_property(788 collection: &CollectionHandle<T>,789 sender: &T::CrossAccountId,790 property_key: PropertyKey,791 ) -> DispatchResult {792 collection.check_is_owner_or_admin(sender)?;793794 CollectionProperties::<T>::try_mutate(collection.id, |properties| {795 properties.remove(&property_key)796 })797 .map_err(<Error<T>>::from)?;798799 Self::deposit_event(Event::CollectionPropertyDeleted(800 collection.id,801 property_key,802 ));803804 Ok(())805 }806807 pub fn delete_collection_properties(808 collection: &CollectionHandle<T>,809 sender: &T::CrossAccountId,810 property_keys: Vec<PropertyKey>,811 ) -> DispatchResult {812 for key in property_keys {813 Self::delete_collection_property(collection, sender, key)?;814 }815816 Ok(())817 }818819 pub fn set_property_permission(820 collection: &CollectionHandle<T>,821 sender: &T::CrossAccountId,822 property_permission: PropertyKeyPermission,823 ) -> DispatchResult {824 collection.check_is_owner_or_admin(sender)?;825826 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);827 let current_permission = all_permissions.get(&property_permission.key);828 if matches![829 current_permission,830 Some(PropertyPermission { mutable: false, .. })831 ] {832 return Err(<Error<T>>::NoPermission.into());833 }834835 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {836 let property_permission = property_permission.clone();837 permissions.try_set(property_permission.key, property_permission.permission)838 })839 .map_err(<Error<T>>::from)?;840841 Self::deposit_event(Event::PropertyPermissionSet(842 collection.id,843 property_permission.key,844 ));845846 Ok(())847 }848849 pub fn set_property_permissions(850 collection: &CollectionHandle<T>,851 sender: &T::CrossAccountId,852 property_permissions: Vec<PropertyKeyPermission>,853 ) -> DispatchResult {854 for prop_pemission in property_permissions {855 Self::set_property_permission(collection, sender, prop_pemission)?;856 }857858 Ok(())859 }860861 pub fn bytes_keys_to_property_keys(862 keys: Vec<Vec<u8>>,863 ) -> Result<Vec<PropertyKey>, DispatchError> {864 keys.into_iter()865 .map(|key| -> Result<PropertyKey, DispatchError> {866 key.try_into()867 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())868 })869 .collect::<Result<Vec<PropertyKey>, DispatchError>>()870 }871872 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {873 let key_str = sp_std::str::from_utf8(key.as_slice())874 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;875876 for ch in key_str.chars() {877 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {878 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());879 }880 }881882 Ok(())883 }884885 pub fn filter_collection_properties(886 collection_id: CollectionId,887 keys: Vec<PropertyKey>,888 ) -> Result<Vec<Property>, DispatchError> {889 let properties = Self::collection_properties(collection_id);890891 let properties = keys892 .into_iter()893 .filter_map(|key| {894 properties.get(&key).map(|value| Property {895 key,896 value: value.clone(),897 })898 })899 .collect();900901 Ok(properties)902 }903904 pub fn filter_property_permissions(905 collection_id: CollectionId,906 keys: Vec<PropertyKey>,907 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {908 let permissions = Self::property_permissions(collection_id);909910 let key_permissions = keys911 .into_iter()912 .filter_map(|key| {913 permissions914 .get(&key)915 .map(|permission| PropertyKeyPermission {916 key,917 permission: permission.clone(),918 })919 })920 .collect();921922 Ok(key_permissions)923 }924925 fn set_field_raw(926 collection_id: CollectionId,927 field: CollectionField,928 value: Vec<u8>,929 ) -> DispatchResult {930 if !value.is_empty() {931 <CollectionData<T>>::insert(932 (collection_id, field),933 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,934 )935 } else {936 <CollectionData<T>>::remove((collection_id, field));937 }938 Ok(())939 }940941 pub fn set_field(942 collection: &CollectionHandle<T>,943 sender: &T::CrossAccountId,944 field: CollectionField,945 value: Vec<u8>,946 ) -> DispatchResult {947 collection.check_is_owner_or_admin(sender)?;948949 950951 Self::set_field_raw(collection.id, field, value)952 }953954 pub fn toggle_allowlist(955 collection: &CollectionHandle<T>,956 sender: &T::CrossAccountId,957 user: &T::CrossAccountId,958 allowed: bool,959 ) -> DispatchResult {960 collection.check_is_owner_or_admin(sender)?;961962 963964 if allowed {965 <Allowlist<T>>::insert((collection.id, user), true);966 } else {967 <Allowlist<T>>::remove((collection.id, user));968 }969970 Ok(())971 }972973 pub fn toggle_admin(974 collection: &CollectionHandle<T>,975 sender: &T::CrossAccountId,976 user: &T::CrossAccountId,977 admin: bool,978 ) -> DispatchResult {979 collection.check_is_owner_or_admin(sender)?;980981 let was_admin = <IsAdmin<T>>::get((collection.id, user));982 if was_admin == admin {983 return Ok(());984 }985 let amount = <AdminAmount<T>>::get(collection.id);986987 if admin {988 let amount = amount989 .checked_add(1)990 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;991 ensure!(992 amount <= Self::collection_admins_limit(),993 <Error<T>>::CollectionAdminCountExceeded,994 );995996 997998 <AdminAmount<T>>::insert(collection.id, amount);999 <IsAdmin<T>>::insert((collection.id, user), true);1000 } else {1001 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1002 <IsAdmin<T>>::remove((collection.id, user));1003 }10041005 Ok(())1006 }10071008 pub fn clamp_limits(1009 mode: CollectionMode,1010 old_limit: &CollectionLimits,1011 mut new_limit: CollectionLimits,1012 ) -> Result<CollectionLimits, DispatchError> {1013 macro_rules! limit_default {1014 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1015 $(1016 if let Some($new) = $new.$field {1017 let $old = $old.$field($($arg)?);1018 let _ = $new;1019 let _ = $old;1020 $check1021 } else {1022 $new.$field = $old.$field1023 }1024 )*1025 }};1026 }10271028 limit_default!(old_limit, new_limit,1029 account_token_ownership_limit => ensure!(1030 new_limit <= MAX_TOKEN_OWNERSHIP,1031 <Error<T>>::CollectionLimitBoundsExceeded,1032 ),1033 sponsor_transfer_timeout(match mode {1034 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1035 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1036 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1037 }) => ensure!(1038 new_limit <= MAX_SPONSOR_TIMEOUT,1039 <Error<T>>::CollectionLimitBoundsExceeded,1040 ),1041 sponsored_data_size => ensure!(1042 new_limit <= CUSTOM_DATA_LIMIT,1043 <Error<T>>::CollectionLimitBoundsExceeded,1044 ),1045 token_limit => ensure!(1046 old_limit >= new_limit && new_limit > 0,1047 <Error<T>>::CollectionTokenLimitExceeded1048 ),1049 owner_can_transfer => ensure!(1050 old_limit || !new_limit,1051 <Error<T>>::OwnerPermissionsCantBeReverted,1052 ),1053 owner_can_destroy => ensure!(1054 old_limit || !new_limit,1055 <Error<T>>::OwnerPermissionsCantBeReverted,1056 ),1057 sponsored_data_rate_limit => {},1058 transfers_enabled => {},1059 );1060 Ok(new_limit)1061 }1062}10631064#[macro_export]1065macro_rules! unsupported {1066 () => {1067 Err(<Error<T>>::UnsupportedOperation.into())1068 };1069}107010711072pub trait CommonWeightInfo<CrossAccountId> {1073 fn create_item() -> Weight;1074 fn create_multiple_items(amount: u32) -> Weight;1075 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1076 fn burn_item() -> Weight;1077 fn set_collection_properties(amount: u32) -> Weight;1078 fn delete_collection_properties(amount: u32) -> Weight;1079 fn set_token_properties(amount: u32) -> Weight;1080 fn delete_token_properties(amount: u32) -> Weight;1081 fn set_property_permissions(amount: u32) -> Weight;1082 fn transfer() -> Weight;1083 fn approve() -> Weight;1084 fn transfer_from() -> Weight;1085 fn burn_from() -> Weight;1086 fn set_variable_metadata(bytes: u32) -> Weight;1087}10881089pub trait CommonCollectionOperations<T: Config> {1090 fn create_item(1091 &self,1092 sender: T::CrossAccountId,1093 to: T::CrossAccountId,1094 data: CreateItemData,1095 nesting_budget: &dyn Budget,1096 ) -> DispatchResultWithPostInfo;1097 fn create_multiple_items(1098 &self,1099 sender: T::CrossAccountId,1100 to: T::CrossAccountId,1101 data: Vec<CreateItemData>,1102 nesting_budget: &dyn Budget,1103 ) -> DispatchResultWithPostInfo;1104 fn create_multiple_items_ex(1105 &self,1106 sender: T::CrossAccountId,1107 data: CreateItemExData<T::CrossAccountId>,1108 nesting_budget: &dyn Budget,1109 ) -> DispatchResultWithPostInfo;1110 fn burn_item(1111 &self,1112 sender: T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 ) -> DispatchResultWithPostInfo;1116 fn set_collection_properties(1117 &self,1118 sender: T::CrossAccountId,1119 properties: Vec<Property>,1120 ) -> DispatchResultWithPostInfo;1121 fn delete_collection_properties(1122 &self,1123 sender: &T::CrossAccountId,1124 property_keys: Vec<PropertyKey>,1125 ) -> DispatchResultWithPostInfo;1126 fn set_token_properties(1127 &self,1128 sender: T::CrossAccountId,1129 token_id: TokenId,1130 property: Vec<Property>,1131 ) -> DispatchResultWithPostInfo;1132 fn delete_token_properties(1133 &self,1134 sender: T::CrossAccountId,1135 token_id: TokenId,1136 property_keys: Vec<PropertyKey>,1137 ) -> DispatchResultWithPostInfo;1138 fn set_property_permissions(1139 &self,1140 sender: &T::CrossAccountId,1141 property_permissions: Vec<PropertyKeyPermission>,1142 ) -> DispatchResultWithPostInfo;1143 fn transfer(1144 &self,1145 sender: T::CrossAccountId,1146 to: T::CrossAccountId,1147 token: TokenId,1148 amount: u128,1149 nesting_budget: &dyn Budget,1150 ) -> DispatchResultWithPostInfo;1151 fn approve(1152 &self,1153 sender: T::CrossAccountId,1154 spender: T::CrossAccountId,1155 token: TokenId,1156 amount: u128,1157 ) -> DispatchResultWithPostInfo;1158 fn transfer_from(1159 &self,1160 sender: T::CrossAccountId,1161 from: T::CrossAccountId,1162 to: T::CrossAccountId,1163 token: TokenId,1164 amount: u128,1165 nesting_budget: &dyn Budget,1166 ) -> DispatchResultWithPostInfo;1167 fn burn_from(1168 &self,1169 sender: T::CrossAccountId,1170 from: T::CrossAccountId,1171 token: TokenId,1172 amount: u128,1173 nesting_budget: &dyn Budget,1174 ) -> DispatchResultWithPostInfo;11751176 fn set_variable_metadata(1177 &self,1178 sender: T::CrossAccountId,1179 token: TokenId,1180 data: BoundedVec<u8, CustomDataLimit>,1181 ) -> DispatchResultWithPostInfo;11821183 fn check_nesting(1184 &self,1185 sender: T::CrossAccountId,1186 from: (CollectionId, TokenId),1187 under: TokenId,1188 budget: &dyn Budget,1189 ) -> DispatchResult;11901191 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1192 fn collection_tokens(&self) -> Vec<TokenId>;1193 fn token_exists(&self, token: TokenId) -> bool;1194 fn last_token_id(&self) -> TokenId;11951196 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1197 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1198 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1199 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1200 1201 fn total_supply(&self) -> u32;1202 1203 fn account_balance(&self, account: T::CrossAccountId) -> u32;1204 1205 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1206 fn allowance(1207 &self,1208 sender: T::CrossAccountId,1209 spender: T::CrossAccountId,1210 token: TokenId,1211 ) -> u128;1212}121312141215pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1216 let post_info = PostDispatchInfo {1217 actual_weight: Some(weight),1218 pays_fee: Pays::Yes,1219 };1220 match res {1221 Ok(()) => Ok(post_info),1222 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1223 }1224}12251226impl<T: Config> From<PropertiesError> for Error<T> {1227 fn from(error: PropertiesError) -> Self {1228 match error {1229 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1230 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1231 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1232 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1233 }1234 }1235}