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, Property),294295 CollectionPropertyDeleted(CollectionId, PropertyKey),296297 TokenPropertySet(CollectionId, TokenId, Property),298299 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),300301 PropertyPermissionSet(CollectionId, PropertyKeyPermission),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,380 }381382 #[pallet::storage]383 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;384 #[pallet::storage]385 pub type DestroyedCollectionCount<T> =386 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;387388 389 #[pallet::storage]390 pub type CollectionById<T> = StorageMap<391 Hasher = Blake2_128Concat,392 Key = CollectionId,393 Value = Collection<<T as frame_system::Config>::AccountId>,394 QueryKind = OptionQuery,395 >;396397 398 #[pallet::storage]399 #[pallet::getter(fn collection_properties)]400 pub type CollectionProperties<T> = StorageMap<401 Hasher = Blake2_128Concat,402 Key = CollectionId,403 Value = Properties,404 QueryKind = ValueQuery,405 OnEmpty = up_data_structs::CollectionProperties,406 >;407408 #[pallet::storage]409 #[pallet::getter(fn property_permissions)]410 pub type CollectionPropertyPermissions<T> = StorageMap<411 Hasher = Blake2_128Concat,412 Key = CollectionId,413 Value = PropertiesPermissionMap,414 QueryKind = ValueQuery,415 >;416417 418 #[pallet::storage]419 pub type CollectionData<T> = StorageNMap<420 Key = (421 Key<Twox64Concat, CollectionId>,422 Key<Twox64Concat, CollectionField>,423 ),424 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,425 QueryKind = ValueQuery,426 >;427428 #[pallet::storage]429 pub type AdminAmount<T> = StorageMap<430 Hasher = Blake2_128Concat,431 Key = CollectionId,432 Value = u32,433 QueryKind = ValueQuery,434 >;435436 437 #[pallet::storage]438 pub type IsAdmin<T: Config> = StorageNMap<439 Key = (440 Key<Blake2_128Concat, CollectionId>,441 Key<Blake2_128Concat, T::CrossAccountId>,442 ),443 Value = bool,444 QueryKind = ValueQuery,445 >;446447 448 #[pallet::storage]449 pub type Allowlist<T: Config> = StorageNMap<450 Key = (451 Key<Blake2_128Concat, CollectionId>,452 Key<Blake2_128Concat, T::CrossAccountId>,453 ),454 Value = bool,455 QueryKind = ValueQuery,456 >;457458 459 #[pallet::storage]460 pub type DummyStorageValue<T: Config> = StorageValue<461 Value = (462 CollectionStats,463 CollectionId,464 TokenId,465 PhantomType<TokenData<T::CrossAccountId>>,466 PhantomType<RpcCollection<T::AccountId>>,467 ),468 QueryKind = OptionQuery,469 >;470471 #[pallet::hooks]472 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {473 fn on_runtime_upgrade() -> Weight {474 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {475 use up_data_structs::{CollectionVersion1, CollectionVersion2};476 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {477 Self::set_field_raw(478 id,479 CollectionField::OffchainSchema,480 v.offchain_schema.clone().into_inner(),481 )482 .expect("data has lower bounds than field");483 Self::set_field_raw(484 id,485 CollectionField::ConstOnChainSchema,486 v.const_on_chain_schema.clone().into_inner(),487 )488 .expect("data has lower bounds than field");489490 Some(CollectionVersion2::from(v))491 });492 }493494 0495 }496 }497}498499impl<T: Config> Pallet<T> {500 501 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {502 ensure!(503 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,504 <Error<T>>::AddressIsZero505 );506 Ok(())507 }508 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {509 <IsAdmin<T>>::iter_prefix((collection,))510 .map(|(a, _)| a)511 .collect()512 }513 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {514 <Allowlist<T>>::iter_prefix((collection,))515 .map(|(a, _)| a)516 .collect()517 }518 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {519 <Allowlist<T>>::get((collection, user))520 }521 pub fn collection_stats() -> CollectionStats {522 let created = <CreatedCollectionCount<T>>::get();523 let destroyed = <DestroyedCollectionCount<T>>::get();524 CollectionStats {525 created: created.0,526 destroyed: destroyed.0,527 alive: created.0 - destroyed.0,528 }529 }530531 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {532 let collection = <CollectionById<T>>::get(collection);533 if collection.is_none() {534 return None;535 }536537 let collection = collection.unwrap();538 let limits = collection.limits;539 let effective_limits = CollectionLimits {540 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),541 sponsored_data_size: Some(limits.sponsored_data_size()),542 sponsored_data_rate_limit: Some(543 limits544 .sponsored_data_rate_limit545 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),546 ),547 token_limit: Some(limits.token_limit()),548 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(549 match collection.mode {550 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,551 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,552 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,553 },554 )),555 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),556 owner_can_transfer: Some(limits.owner_can_transfer()),557 owner_can_destroy: Some(limits.owner_can_destroy()),558 transfers_enabled: Some(limits.transfers_enabled()),559 nesting_rule: Some(limits.nesting_rule().clone()),560 };561562 Some(effective_limits)563 }564565 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {566 let Collection {567 name,568 description,569 owner,570 mode,571 access,572 token_prefix,573 mint_mode,574 schema_version,575 sponsorship,576 limits,577 meta_update_permission,578 } = <CollectionById<T>>::get(collection)?;579580 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)581 .iter()582 .map(|(key, permission)| PropertyKeyPermission {583 key: key.clone(),584 permission: permission.clone(),585 })586 .collect();587588 let properties = <CollectionProperties<T>>::get(collection)589 .iter()590 .map(|(key, value)| Property {591 key: key.clone(),592 value: value.clone(),593 })594 .collect();595596 Some(RpcCollection {597 name: name.into_inner(),598 description: description.into_inner(),599 owner,600 mode,601 access,602 token_prefix: token_prefix.into_inner(),603 mint_mode,604 schema_version,605 sponsorship,606 limits,607 meta_update_permission,608 offchain_schema: <CollectionData<T>>::get((609 collection,610 CollectionField::OffchainSchema,611 ))612 .into_inner(),613 const_on_chain_schema: <CollectionData<T>>::get((614 collection,615 CollectionField::ConstOnChainSchema,616 ))617 .into_inner(),618 token_property_permissions,619 properties,620 })621 }622}623624impl<T: Config> Pallet<T> {625 pub fn init_collection(626 owner: T::AccountId,627 data: CreateCollectionData<T::AccountId>,628 ) -> Result<CollectionId, DispatchError> {629 {630 ensure!(631 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,632 Error::<T>::CollectionTokenPrefixLimitExceeded633 );634 }635636 let created_count = <CreatedCollectionCount<T>>::get()637 .0638 .checked_add(1)639 .ok_or(ArithmeticError::Overflow)?;640 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;641 let id = CollectionId(created_count);642643 644 ensure!(645 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,646 <Error<T>>::TotalCollectionsLimitExceeded647 );648649 650651 let collection = Collection {652 owner: owner.clone(),653 name: data.name,654 mode: data.mode.clone(),655 mint_mode: false,656 access: data.access.unwrap_or_default(),657 description: data.description,658 token_prefix: data.token_prefix,659 schema_version: data.schema_version.unwrap_or_default(),660 sponsorship: data661 .pending_sponsor662 .map(SponsorshipState::Unconfirmed)663 .unwrap_or_default(),664 limits: data665 .limits666 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))667 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,668 meta_update_permission: data.meta_update_permission.unwrap_or_default(),669 };670671 let mut collection_properties = up_data_structs::CollectionProperties::get();672 collection_properties673 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))674 .map_err(|e| -> Error<T> { e.into() })?;675676 CollectionProperties::<T>::insert(id, collection_properties);677678 let mut token_props_permissions = PropertiesPermissionMap::new();679 token_props_permissions680 .try_set_from_iter(681 data.token_property_permissions682 .into_iter()683 .map(|property| (property.key, property.permission)),684 )685 .map_err(|e| -> Error<T> { e.into() })?;686687 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);688689 690 {691 let mut imbalance =692 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();693 imbalance.subsume(694 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(695 &T::TreasuryAccountId::get(),696 T::CollectionCreationPrice::get(),697 ),698 );699 <T as Config>::Currency::settle(700 &owner,701 imbalance,702 WithdrawReasons::TRANSFER,703 ExistenceRequirement::KeepAlive,704 )705 .map_err(|_| Error::<T>::NotSufficientFounds)?;706 }707708 <CreatedCollectionCount<T>>::put(created_count);709 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));710 <CollectionById<T>>::insert(id, collection);711 Self::set_field_raw(712 id,713 CollectionField::OffchainSchema,714 data.offchain_schema.into_inner(),715 )716 .expect("data has lower bounds than field");717 Self::set_field_raw(718 id,719 CollectionField::ConstOnChainSchema,720 data.const_on_chain_schema.into_inner(),721 )722 .expect("data has lower bounds than field");723 Ok(id)724 }725726 pub fn destroy_collection(727 collection: CollectionHandle<T>,728 sender: &T::CrossAccountId,729 ) -> DispatchResult {730 ensure!(731 collection.limits.owner_can_destroy(),732 <Error<T>>::NoPermission,733 );734 collection.check_is_owner(sender)?;735736 let destroyed_collections = <DestroyedCollectionCount<T>>::get()737 .0738 .checked_add(1)739 .ok_or(ArithmeticError::Overflow)?;740741 742743 <DestroyedCollectionCount<T>>::put(destroyed_collections);744 <CollectionById<T>>::remove(collection.id);745 <CollectionData<T>>::remove_prefix((collection.id,), None);746 <AdminAmount<T>>::remove(collection.id);747 <IsAdmin<T>>::remove_prefix((collection.id,), None);748 <Allowlist<T>>::remove_prefix((collection.id,), None);749750 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));751 Ok(())752 }753754 pub fn set_collection_property(755 collection: &CollectionHandle<T>,756 sender: &T::CrossAccountId,757 property: Property,758 ) -> DispatchResult {759 collection.check_is_owner_or_admin(sender)?;760761 CollectionProperties::<T>::try_mutate(collection.id, |properties| {762 let property = property.clone();763 properties.try_set(property.key, property.value)764 })765 .map_err(|e| -> Error<T> { e.into() })?;766767 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));768769 Ok(())770 }771772 pub fn set_collection_properties(773 collection: &CollectionHandle<T>,774 sender: &T::CrossAccountId,775 properties: Vec<Property>,776 ) -> DispatchResult {777 for property in properties {778 Self::set_collection_property(collection, sender, property)?;779 }780781 Ok(())782 }783784 pub fn delete_collection_property(785 collection: &CollectionHandle<T>,786 sender: &T::CrossAccountId,787 property_key: PropertyKey,788 ) -> DispatchResult {789 collection.check_is_owner_or_admin(sender)?;790791 CollectionProperties::<T>::try_mutate(collection.id, |properties| {792 properties.remove(&property_key)793 })794 .map_err(|e| -> Error<T> { e.into() })?;795796 Self::deposit_event(Event::CollectionPropertyDeleted(797 collection.id,798 property_key,799 ));800801 Ok(())802 }803804 pub fn delete_collection_properties(805 collection: &CollectionHandle<T>,806 sender: &T::CrossAccountId,807 property_keys: Vec<PropertyKey>,808 ) -> DispatchResult {809 for key in property_keys {810 Self::delete_collection_property(collection, sender, key)?;811 }812813 Ok(())814 }815816 pub fn set_property_permission(817 collection: &CollectionHandle<T>,818 sender: &T::CrossAccountId,819 property_permission: PropertyKeyPermission,820 ) -> DispatchResult {821 collection.check_is_owner_or_admin(sender)?;822823 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);824 let current_permission = all_permissions.get(&property_permission.key);825 if matches![826 current_permission,827 Some(PropertyPermission { mutable: false, .. })828 ] {829 return Err(<Error<T>>::NoPermission.into());830 }831832 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {833 let property_permission = property_permission.clone();834 permissions.try_set(property_permission.key, property_permission.permission)835 })836 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;837838 Self::deposit_event(Event::PropertyPermissionSet(839 collection.id,840 property_permission,841 ));842843 Ok(())844 }845846 pub fn set_property_permissions(847 collection: &CollectionHandle<T>,848 sender: &T::CrossAccountId,849 property_permissions: Vec<PropertyKeyPermission>,850 ) -> DispatchResult {851 for prop_pemission in property_permissions {852 Self::set_property_permission(collection, sender, prop_pemission)?;853 }854855 Ok(())856 }857858 pub fn bytes_keys_to_property_keys(859 keys: Vec<Vec<u8>>,860 ) -> Result<Vec<PropertyKey>, DispatchError> {861 keys.into_iter()862 .map(|key| -> Result<PropertyKey, DispatchError> {863 key.try_into()864 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())865 })866 .collect::<Result<Vec<PropertyKey>, DispatchError>>()867 }868869 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {870 let key_str = sp_std::str::from_utf8(key.as_slice())871 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;872873 for ch in key_str.chars() {874 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {875 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());876 }877 }878879 Ok(())880 }881882 pub fn filter_collection_properties(883 collection_id: CollectionId,884 keys: Vec<PropertyKey>,885 ) -> Result<Vec<Property>, DispatchError> {886 let properties = Self::collection_properties(collection_id);887888 let properties = keys889 .into_iter()890 .filter_map(|key| {891 properties.get(&key).map(|value| Property {892 key,893 value: value.clone(),894 })895 })896 .collect();897898 Ok(properties)899 }900901 pub fn filter_property_permissions(902 collection_id: CollectionId,903 keys: Vec<PropertyKey>,904 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {905 let permissions = Self::property_permissions(collection_id);906907 let key_permissions = keys908 .into_iter()909 .filter_map(|key| {910 permissions911 .get(&key)912 .map(|permission| PropertyKeyPermission {913 key,914 permission: permission.clone(),915 })916 })917 .collect();918919 Ok(key_permissions)920 }921922 fn set_field_raw(923 collection_id: CollectionId,924 field: CollectionField,925 value: Vec<u8>,926 ) -> DispatchResult {927 if !value.is_empty() {928 <CollectionData<T>>::insert(929 (collection_id, field),930 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,931 )932 } else {933 <CollectionData<T>>::remove((collection_id, field));934 }935 Ok(())936 }937938 pub fn set_field(939 collection: &CollectionHandle<T>,940 sender: &T::CrossAccountId,941 field: CollectionField,942 value: Vec<u8>,943 ) -> DispatchResult {944 collection.check_is_owner_or_admin(sender)?;945946 947948 Self::set_field_raw(collection.id, field, value)949 }950951 pub fn toggle_allowlist(952 collection: &CollectionHandle<T>,953 sender: &T::CrossAccountId,954 user: &T::CrossAccountId,955 allowed: bool,956 ) -> DispatchResult {957 collection.check_is_owner_or_admin(sender)?;958959 960961 if allowed {962 <Allowlist<T>>::insert((collection.id, user), true);963 } else {964 <Allowlist<T>>::remove((collection.id, user));965 }966967 Ok(())968 }969970 pub fn toggle_admin(971 collection: &CollectionHandle<T>,972 sender: &T::CrossAccountId,973 user: &T::CrossAccountId,974 admin: bool,975 ) -> DispatchResult {976 collection.check_is_owner_or_admin(sender)?;977978 let was_admin = <IsAdmin<T>>::get((collection.id, user));979 if was_admin == admin {980 return Ok(());981 }982 let amount = <AdminAmount<T>>::get(collection.id);983984 if admin {985 let amount = amount986 .checked_add(1)987 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;988 ensure!(989 amount <= Self::collection_admins_limit(),990 <Error<T>>::CollectionAdminCountExceeded,991 );992993 994995 <AdminAmount<T>>::insert(collection.id, amount);996 <IsAdmin<T>>::insert((collection.id, user), true);997 } else {998 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));999 <IsAdmin<T>>::remove((collection.id, user));1000 }10011002 Ok(())1003 }10041005 pub fn clamp_limits(1006 mode: CollectionMode,1007 old_limit: &CollectionLimits,1008 mut new_limit: CollectionLimits,1009 ) -> Result<CollectionLimits, DispatchError> {1010 macro_rules! limit_default {1011 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1012 $(1013 if let Some($new) = $new.$field {1014 let $old = $old.$field($($arg)?);1015 let _ = $new;1016 let _ = $old;1017 $check1018 } else {1019 $new.$field = $old.$field1020 }1021 )*1022 }};1023 }10241025 limit_default!(old_limit, new_limit,1026 account_token_ownership_limit => ensure!(1027 new_limit <= MAX_TOKEN_OWNERSHIP,1028 <Error<T>>::CollectionLimitBoundsExceeded,1029 ),1030 sponsor_transfer_timeout(match mode {1031 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1032 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1033 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1034 }) => ensure!(1035 new_limit <= MAX_SPONSOR_TIMEOUT,1036 <Error<T>>::CollectionLimitBoundsExceeded,1037 ),1038 sponsored_data_size => ensure!(1039 new_limit <= CUSTOM_DATA_LIMIT,1040 <Error<T>>::CollectionLimitBoundsExceeded,1041 ),1042 token_limit => ensure!(1043 old_limit >= new_limit && new_limit > 0,1044 <Error<T>>::CollectionTokenLimitExceeded1045 ),1046 owner_can_transfer => ensure!(1047 old_limit || !new_limit,1048 <Error<T>>::OwnerPermissionsCantBeReverted,1049 ),1050 owner_can_destroy => ensure!(1051 old_limit || !new_limit,1052 <Error<T>>::OwnerPermissionsCantBeReverted,1053 ),1054 sponsored_data_rate_limit => {},1055 transfers_enabled => {},1056 );1057 Ok(new_limit)1058 }1059}10601061#[macro_export]1062macro_rules! unsupported {1063 () => {1064 Err(<Error<T>>::UnsupportedOperation.into())1065 };1066}106710681069pub trait CommonWeightInfo<CrossAccountId> {1070 fn create_item() -> Weight;1071 fn create_multiple_items(amount: u32) -> Weight;1072 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1073 fn burn_item() -> Weight;1074 fn set_collection_properties(amount: u32) -> Weight;1075 fn delete_collection_properties(amount: u32) -> Weight;1076 fn set_token_properties(amount: u32) -> Weight;1077 fn delete_token_properties(amount: u32) -> Weight;1078 fn set_property_permissions(amount: u32) -> Weight;1079 fn transfer() -> Weight;1080 fn approve() -> Weight;1081 fn transfer_from() -> Weight;1082 fn burn_from() -> Weight;1083 fn set_variable_metadata(bytes: u32) -> Weight;1084}10851086pub trait CommonCollectionOperations<T: Config> {1087 fn create_item(1088 &self,1089 sender: T::CrossAccountId,1090 to: T::CrossAccountId,1091 data: CreateItemData,1092 nesting_budget: &dyn Budget,1093 ) -> DispatchResultWithPostInfo;1094 fn create_multiple_items(1095 &self,1096 sender: T::CrossAccountId,1097 to: T::CrossAccountId,1098 data: Vec<CreateItemData>,1099 nesting_budget: &dyn Budget,1100 ) -> DispatchResultWithPostInfo;1101 fn create_multiple_items_ex(1102 &self,1103 sender: T::CrossAccountId,1104 data: CreateItemExData<T::CrossAccountId>,1105 nesting_budget: &dyn Budget,1106 ) -> DispatchResultWithPostInfo;1107 fn burn_item(1108 &self,1109 sender: T::CrossAccountId,1110 token: TokenId,1111 amount: u128,1112 ) -> DispatchResultWithPostInfo;1113 fn set_collection_properties(1114 &self,1115 sender: T::CrossAccountId,1116 properties: Vec<Property>,1117 ) -> DispatchResultWithPostInfo;1118 fn delete_collection_properties(1119 &self,1120 sender: &T::CrossAccountId,1121 property_keys: Vec<PropertyKey>,1122 ) -> DispatchResultWithPostInfo;1123 fn set_token_properties(1124 &self,1125 sender: T::CrossAccountId,1126 token_id: TokenId,1127 property: Vec<Property>,1128 ) -> DispatchResultWithPostInfo;1129 fn delete_token_properties(1130 &self,1131 sender: T::CrossAccountId,1132 token_id: TokenId,1133 property_keys: Vec<PropertyKey>,1134 ) -> DispatchResultWithPostInfo;1135 fn set_property_permissions(1136 &self,1137 sender: &T::CrossAccountId,1138 property_permissions: Vec<PropertyKeyPermission>,1139 ) -> DispatchResultWithPostInfo;1140 fn transfer(1141 &self,1142 sender: T::CrossAccountId,1143 to: T::CrossAccountId,1144 token: TokenId,1145 amount: u128,1146 nesting_budget: &dyn Budget,1147 ) -> DispatchResultWithPostInfo;1148 fn approve(1149 &self,1150 sender: T::CrossAccountId,1151 spender: T::CrossAccountId,1152 token: TokenId,1153 amount: u128,1154 ) -> DispatchResultWithPostInfo;1155 fn transfer_from(1156 &self,1157 sender: T::CrossAccountId,1158 from: T::CrossAccountId,1159 to: T::CrossAccountId,1160 token: TokenId,1161 amount: u128,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn burn_from(1165 &self,1166 sender: T::CrossAccountId,1167 from: T::CrossAccountId,1168 token: TokenId,1169 amount: u128,1170 nesting_budget: &dyn Budget,1171 ) -> DispatchResultWithPostInfo;11721173 fn set_variable_metadata(1174 &self,1175 sender: T::CrossAccountId,1176 token: TokenId,1177 data: BoundedVec<u8, CustomDataLimit>,1178 ) -> DispatchResultWithPostInfo;11791180 fn check_nesting(1181 &self,1182 sender: T::CrossAccountId,1183 from: (CollectionId, TokenId),1184 under: TokenId,1185 budget: &dyn Budget,1186 ) -> DispatchResult;11871188 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1189 fn collection_tokens(&self) -> Vec<TokenId>;1190 fn token_exists(&self, token: TokenId) -> bool;1191 fn last_token_id(&self) -> TokenId;11921193 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1194 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1195 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1196 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1197 1198 fn total_supply(&self) -> u32;1199 1200 fn account_balance(&self, account: T::CrossAccountId) -> u32;1201 1202 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1203 fn allowance(1204 &self,1205 sender: T::CrossAccountId,1206 spender: T::CrossAccountId,1207 token: TokenId,1208 ) -> u128;1209}121012111212pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1213 let post_info = PostDispatchInfo {1214 actual_weight: Some(weight),1215 pays_fee: Pays::Yes,1216 };1217 match res {1218 Ok(()) => Ok(post_info),1219 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1220 }1221}12221223impl<T: Config> From<PropertiesError> for Error<T> {1224 fn from(error: PropertiesError) -> Self {1225 match error {1226 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1227 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1228 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1229 }1230 }1231}