1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156157 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158 if self.collection.sponsorship.pending_sponsor() != Some(sender) {159 return false;160 };161162 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163 true164 }165}166impl<T: Config> Deref for CollectionHandle<T> {167 type Target = Collection<T::AccountId>;168169 fn deref(&self) -> &Self::Target {170 &self.collection171 }172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175 fn deref_mut(&mut self) -> &mut Self::Target {176 &mut self.collection177 }178}179180impl<T: Config> CollectionHandle<T> {181 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183 Ok(())184 }185 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187 }188 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190 Ok(())191 }192 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194 }195 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197 }198 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199 ensure!(200 <Allowlist<T>>::get((self.id, user)),201 <Error<T>>::AddressNotInAllowlist202 );203 Ok(())204 }205}206207#[frame_support::pallet]208pub mod pallet {209 use super::*;210 use pallet_evm::account;211 use dispatch::CollectionDispatch;212 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213 use frame_system::pallet_prelude::*;214 use frame_support::traits::Currency;215 use up_data_structs::{TokenId, mapping::TokenAddressMapping};216 use scale_info::TypeInfo;217 use weights::WeightInfo;218219 #[pallet::config]220 pub trait Config:221 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config222 {223 type WeightInfo: WeightInfo;224 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;225226 type Currency: Currency<Self::AccountId>;227228 #[pallet::constant]229 type CollectionCreationPrice: Get<230 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,231 >;232 type CollectionDispatch: CollectionDispatch<Self>;233234 type TreasuryAccountId: Get<Self::AccountId>;235236 type EvmTokenAddressMapping: TokenAddressMapping<H160>;237 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;238 }239240 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);241242 #[pallet::pallet]243 #[pallet::storage_version(STORAGE_VERSION)]244 #[pallet::generate_store(pub(super) trait Store)]245 pub struct Pallet<T>(_);246247 #[pallet::extra_constants]248 impl<T: Config> Pallet<T> {249 pub fn collection_admins_limit() -> u32 {250 COLLECTION_ADMINS_LIMIT251 }252 }253254 #[pallet::event]255 #[pallet::generate_deposit(pub fn deposit_event)]256 pub enum Event<T: Config> {257 258 259 260 261 262 263 264 265 266 CollectionCreated(CollectionId, u8, T::AccountId),267268 269 270 271 272 273 CollectionDestroyed(CollectionId),274275 276 277 278 279 280 281 282 283 284 285 286 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),287288 289 290 291 292 293 294 295 296 297 298 299 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),300301 302 303 304 305 306 307 308 309 310 311 312 Transfer(313 CollectionId,314 TokenId,315 T::CrossAccountId,316 T::CrossAccountId,317 u128,318 ),319320 321 322 323 324 325 326 327 328 329 Approved(330 CollectionId,331 TokenId,332 T::CrossAccountId,333 T::CrossAccountId,334 u128,335 ),336337 CollectionPropertySet(CollectionId, PropertyKey),338339 CollectionPropertyDeleted(CollectionId, PropertyKey),340341 TokenPropertySet(CollectionId, TokenId, PropertyKey),342343 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),344345 PropertyPermissionSet(CollectionId, PropertyKey),346 }347348 #[pallet::error]349 pub enum Error<T> {350 351 CollectionNotFound,352 353 MustBeTokenOwner,354 355 NoPermission,356 357 PublicMintingNotAllowed,358 359 AddressNotInAllowlist,360361 362 CollectionNameLimitExceeded,363 364 CollectionDescriptionLimitExceeded,365 366 CollectionTokenPrefixLimitExceeded,367 368 TotalCollectionsLimitExceeded,369 370 CollectionAdminCountExceeded,371 372 CollectionLimitBoundsExceeded,373 374 OwnerPermissionsCantBeReverted,375 376 TransferNotAllowed,377 378 AccountTokenLimitExceeded,379 380 CollectionTokenLimitExceeded,381 382 MetadataFlagFrozen,383384 385 TokenNotFound,386 387 TokenValueTooLow,388 389 ApprovedValueTooLow,390 391 CantApproveMoreThanOwned,392393 394 AddressIsZero,395 396 UnsupportedOperation,397398 399 NotSufficientFounds,400401 402 NestingIsDisabled,403 404 OnlyOwnerAllowedToNest,405 406 SourceCollectionIsNotAllowedToNest,407408 409 CollectionFieldSizeExceeded,410411 412 NoSpaceForProperty,413414 415 PropertyLimitReached,416417 418 PropertyKeyIsTooLong,419420 421 InvalidCharacterInPropertyKey,422423 424 EmptyPropertyKey,425 }426427 #[pallet::storage]428 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;429 #[pallet::storage]430 pub type DestroyedCollectionCount<T> =431 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;432433 434 #[pallet::storage]435 pub type CollectionById<T> = StorageMap<436 Hasher = Blake2_128Concat,437 Key = CollectionId,438 Value = Collection<<T as frame_system::Config>::AccountId>,439 QueryKind = OptionQuery,440 >;441442 443 #[pallet::storage]444 #[pallet::getter(fn collection_properties)]445 pub type CollectionProperties<T> = StorageMap<446 Hasher = Blake2_128Concat,447 Key = CollectionId,448 Value = Properties,449 QueryKind = ValueQuery,450 OnEmpty = up_data_structs::CollectionProperties,451 >;452453 #[pallet::storage]454 #[pallet::getter(fn property_permissions)]455 pub type CollectionPropertyPermissions<T> = StorageMap<456 Hasher = Blake2_128Concat,457 Key = CollectionId,458 Value = PropertiesPermissionMap,459 QueryKind = ValueQuery,460 >;461462 #[pallet::storage]463 pub type AdminAmount<T> = StorageMap<464 Hasher = Blake2_128Concat,465 Key = CollectionId,466 Value = u32,467 QueryKind = ValueQuery,468 >;469470 471 #[pallet::storage]472 pub type IsAdmin<T: Config> = StorageNMap<473 Key = (474 Key<Blake2_128Concat, CollectionId>,475 Key<Blake2_128Concat, T::CrossAccountId>,476 ),477 Value = bool,478 QueryKind = ValueQuery,479 >;480481 482 #[pallet::storage]483 pub type Allowlist<T: Config> = StorageNMap<484 Key = (485 Key<Blake2_128Concat, CollectionId>,486 Key<Blake2_128Concat, T::CrossAccountId>,487 ),488 Value = bool,489 QueryKind = ValueQuery,490 >;491492 493 #[pallet::storage]494 pub type DummyStorageValue<T: Config> = StorageValue<495 Value = (496 CollectionStats,497 CollectionId,498 TokenId,499 PhantomType<(500 TokenData<T::CrossAccountId>,501 RpcCollection<T::AccountId>,502503 504 RmrkCollectionInfo<T::AccountId>,505 RmrkInstanceInfo<T::AccountId>,506 RmrkResourceInfo,507 RmrkPropertyInfo,508 RmrkBaseInfo<T::AccountId>,509 RmrkPartType,510 RmrkTheme,511 RmrkNftChild,512 )>,513 ),514 QueryKind = OptionQuery,515 >;516517 #[pallet::hooks]518 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {519 fn on_runtime_upgrade() -> Weight {520 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {521 use up_data_structs::{CollectionVersion1, CollectionVersion2};522 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {523 let mut props = Vec::new();524 if !v.offchain_schema.is_empty() {525 props.push(Property {526 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),527 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),528 });529 }530 if !v.variable_on_chain_schema.is_empty() {531 props.push(Property {532 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),533 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),534 });535 }536 if !v.const_on_chain_schema.is_empty() {537 props.push(Property {538 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),539 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),540 });541 }542 props.push(Property {543 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),544 value: match v.schema_version {545 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),546 SchemaVersion::Unique => b"Unique".as_slice(),547 }.to_vec().try_into().unwrap(),548 });549 Self::set_scoped_collection_properties(550 id,551 PropertyScope::None,552 props.into_iter(),553 ).expect("existing data larger than properties");554 let mut new = CollectionVersion2::from(v.clone());555 new.permissions.access = Some(v.access);556 new.permissions.mint_mode = Some(v.mint_mode);557 Some(new)558 });559 }560561 0562 }563 }564}565566impl<T: Config> Pallet<T> {567 568 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {569 ensure!(570 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,571 <Error<T>>::AddressIsZero572 );573 Ok(())574 }575 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {576 <IsAdmin<T>>::iter_prefix((collection,))577 .map(|(a, _)| a)578 .collect()579 }580 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {581 <Allowlist<T>>::iter_prefix((collection,))582 .map(|(a, _)| a)583 .collect()584 }585 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {586 <Allowlist<T>>::get((collection, user))587 }588 pub fn collection_stats() -> CollectionStats {589 let created = <CreatedCollectionCount<T>>::get();590 let destroyed = <DestroyedCollectionCount<T>>::get();591 CollectionStats {592 created: created.0,593 destroyed: destroyed.0,594 alive: created.0 - destroyed.0,595 }596 }597598 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {599 let collection = <CollectionById<T>>::get(collection);600 if collection.is_none() {601 return None;602 }603604 let collection = collection.unwrap();605 let limits = collection.limits;606 let effective_limits = CollectionLimits {607 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),608 sponsored_data_size: Some(limits.sponsored_data_size()),609 sponsored_data_rate_limit: Some(610 limits611 .sponsored_data_rate_limit612 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),613 ),614 token_limit: Some(limits.token_limit()),615 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(616 match collection.mode {617 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,618 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,619 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,620 },621 )),622 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),623 owner_can_transfer: Some(limits.owner_can_transfer()),624 owner_can_destroy: Some(limits.owner_can_destroy()),625 transfers_enabled: Some(limits.transfers_enabled()),626 };627628 Some(effective_limits)629 }630631 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {632 let Collection {633 name,634 description,635 owner,636 mode,637 token_prefix,638 sponsorship,639 limits,640 permissions,641 } = <CollectionById<T>>::get(collection)?;642643 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)644 .into_iter()645 .map(|(key, permission)| PropertyKeyPermission {646 key,647 permission,648 })649 .collect();650651 let properties = <CollectionProperties<T>>::get(collection)652 .into_iter()653 .map(|(key, value)| Property {654 key,655 value,656 })657 .collect();658659 Some(RpcCollection {660 name: name.into_inner(),661 description: description.into_inner(),662 owner,663 mode,664 token_prefix: token_prefix.into_inner(),665 sponsorship,666 limits,667 permissions,668 token_property_permissions,669 properties,670 })671 }672}673674macro_rules! limit_default {675 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{676 $(677 if let Some($new) = $new.$field {678 let $old = $old.$field($($arg)?);679 let _ = $new;680 let _ = $old;681 $check682 } else {683 $new.$field = $old.$field684 }685 )*686 }};687}688macro_rules! limit_default_clone {689 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{690 $(691 if let Some($new) = $new.$field.clone() {692 let $old = $old.$field($($arg)?);693 let _ = $new;694 let _ = $old;695 $check696 } else {697 $new.$field = $old.$field.clone()698 }699 )*700 }};701}702703impl<T: Config> Pallet<T> {704 pub fn init_collection(705 owner: T::AccountId,706 data: CreateCollectionData<T::AccountId>,707 ) -> Result<CollectionId, DispatchError> {708 {709 ensure!(710 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,711 Error::<T>::CollectionTokenPrefixLimitExceeded712 );713 }714715 let created_count = <CreatedCollectionCount<T>>::get()716 .0717 .checked_add(1)718 .ok_or(ArithmeticError::Overflow)?;719 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;720 let id = CollectionId(created_count);721722 723 ensure!(724 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,725 <Error<T>>::TotalCollectionsLimitExceeded726 );727728 729730 let collection = Collection {731 owner: owner.clone(),732 name: data.name,733 mode: data.mode.clone(),734 description: data.description,735 token_prefix: data.token_prefix,736 sponsorship: data737 .pending_sponsor738 .map(SponsorshipState::Unconfirmed)739 .unwrap_or_default(),740 limits: data741 .limits742 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))743 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,744 permissions: data745 .permissions746 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))747 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,748 };749750 let mut collection_properties = up_data_structs::CollectionProperties::get();751 collection_properties752 .try_set_from_iter(data.properties.into_iter())753 .map_err(<Error<T>>::from)?;754755 CollectionProperties::<T>::insert(id, collection_properties);756757 let mut token_props_permissions = PropertiesPermissionMap::new();758 token_props_permissions759 .try_set_from_iter(data.token_property_permissions.into_iter())760 .map_err(<Error<T>>::from)?;761762 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);763764 765 {766 let mut imbalance =767 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();768 imbalance.subsume(769 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(770 &T::TreasuryAccountId::get(),771 T::CollectionCreationPrice::get(),772 ),773 );774 <T as Config>::Currency::settle(775 &owner,776 imbalance,777 WithdrawReasons::TRANSFER,778 ExistenceRequirement::KeepAlive,779 )780 .map_err(|_| Error::<T>::NotSufficientFounds)?;781 }782783 <CreatedCollectionCount<T>>::put(created_count);784 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));785 <CollectionById<T>>::insert(id, collection);786 Ok(id)787 }788789 pub fn destroy_collection(790 collection: CollectionHandle<T>,791 sender: &T::CrossAccountId,792 ) -> DispatchResult {793 ensure!(794 collection.limits.owner_can_destroy(),795 <Error<T>>::NoPermission,796 );797 collection.check_is_owner(sender)?;798799 let destroyed_collections = <DestroyedCollectionCount<T>>::get()800 .0801 .checked_add(1)802 .ok_or(ArithmeticError::Overflow)?;803804 805806 <DestroyedCollectionCount<T>>::put(destroyed_collections);807 <CollectionById<T>>::remove(collection.id);808 <AdminAmount<T>>::remove(collection.id);809 <IsAdmin<T>>::remove_prefix((collection.id,), None);810 <Allowlist<T>>::remove_prefix((collection.id,), None);811 <CollectionProperties<T>>::remove(collection.id);812813 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));814 Ok(())815 }816817 pub fn set_collection_property(818 collection: &CollectionHandle<T>,819 sender: &T::CrossAccountId,820 property: Property,821 ) -> DispatchResult {822 collection.check_is_owner_or_admin(sender)?;823824 CollectionProperties::<T>::try_mutate(collection.id, |properties| {825 let property = property.clone();826 properties.try_set(property.key, property.value)827 })828 .map_err(<Error<T>>::from)?;829830 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));831832 Ok(())833 }834835 pub fn set_scoped_collection_property(836 collection_id: CollectionId,837 scope: PropertyScope,838 property: Property,839 ) -> DispatchResult {840 CollectionProperties::<T>::try_mutate(collection_id, |properties| {841 properties.try_scoped_set(scope, property.key, property.value)842 })843 .map_err(<Error<T>>::from)?;844845 Ok(())846 }847848 pub fn set_scoped_collection_properties(849 collection_id: CollectionId,850 scope: PropertyScope,851 properties: impl Iterator<Item = Property>,852 ) -> DispatchResult {853 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {854 stored_properties.try_scoped_set_from_iter(scope, properties)855 })856 .map_err(<Error<T>>::from)?;857858 Ok(())859 }860861 #[transactional]862 pub fn set_collection_properties(863 collection: &CollectionHandle<T>,864 sender: &T::CrossAccountId,865 properties: Vec<Property>,866 ) -> DispatchResult {867 for property in properties {868 Self::set_collection_property(collection, sender, property)?;869 }870871 Ok(())872 }873874 pub fn delete_collection_property(875 collection: &CollectionHandle<T>,876 sender: &T::CrossAccountId,877 property_key: PropertyKey,878 ) -> DispatchResult {879 collection.check_is_owner_or_admin(sender)?;880881 CollectionProperties::<T>::try_mutate(collection.id, |properties| {882 properties.remove(&property_key)883 })884 .map_err(<Error<T>>::from)?;885886 Self::deposit_event(Event::CollectionPropertyDeleted(887 collection.id,888 property_key,889 ));890891 Ok(())892 }893894 #[transactional]895 pub fn delete_collection_properties(896 collection: &CollectionHandle<T>,897 sender: &T::CrossAccountId,898 property_keys: Vec<PropertyKey>,899 ) -> DispatchResult {900 for key in property_keys {901 Self::delete_collection_property(collection, sender, key)?;902 }903904 Ok(())905 }906907 908 pub fn set_property_permission_unchecked(909 collection: CollectionId,910 property_permission: PropertyKeyPermission,911 ) -> DispatchResult {912 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {913 permissions.try_set(property_permission.key, property_permission.permission)914 })915 .map_err(<Error<T>>::from)?;916 Ok(())917 }918919 pub fn set_property_permission(920 collection: &CollectionHandle<T>,921 sender: &T::CrossAccountId,922 property_permission: PropertyKeyPermission,923 ) -> DispatchResult {924 collection.check_is_owner_or_admin(sender)?;925926 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);927 let current_permission = all_permissions.get(&property_permission.key);928 if matches![929 current_permission,930 Some(PropertyPermission { mutable: false, .. })931 ] {932 return Err(<Error<T>>::NoPermission.into());933 }934935 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {936 let property_permission = property_permission.clone();937 permissions.try_set(property_permission.key, property_permission.permission)938 })939 .map_err(<Error<T>>::from)?;940941 Self::deposit_event(Event::PropertyPermissionSet(942 collection.id,943 property_permission.key,944 ));945946 Ok(())947 }948949 #[transactional]950 pub fn set_property_permissions(951 collection: &CollectionHandle<T>,952 sender: &T::CrossAccountId,953 property_permissions: Vec<PropertyKeyPermission>,954 ) -> DispatchResult {955 for prop_pemission in property_permissions {956 Self::set_property_permission(collection, sender, prop_pemission)?;957 }958959 Ok(())960 }961962 pub fn get_collection_property(963 collection_id: CollectionId,964 key: &PropertyKey,965 ) -> Option<PropertyValue> {966 Self::collection_properties(collection_id).get(key).cloned()967 }968969 pub fn bytes_keys_to_property_keys(970 keys: Vec<Vec<u8>>,971 ) -> Result<Vec<PropertyKey>, DispatchError> {972 keys.into_iter()973 .map(|key| -> Result<PropertyKey, DispatchError> {974 key.try_into()975 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())976 })977 .collect::<Result<Vec<PropertyKey>, DispatchError>>()978 }979980 pub fn filter_collection_properties(981 collection_id: CollectionId,982 keys: Option<Vec<PropertyKey>>,983 ) -> Result<Vec<Property>, DispatchError> {984 let properties = Self::collection_properties(collection_id);985986 let properties = keys987 .map(|keys| {988 keys.into_iter()989 .filter_map(|key| {990 properties.get(&key).map(|value| Property {991 key,992 value: value.clone(),993 })994 })995 .collect()996 })997 .unwrap_or_else(|| {998 properties999 .into_iter()1000 .map(|(key, value)| Property {1001 key,1002 value,1003 })1004 .collect()1005 });10061007 Ok(properties)1008 }10091010 pub fn filter_property_permissions(1011 collection_id: CollectionId,1012 keys: Option<Vec<PropertyKey>>,1013 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1014 let permissions = Self::property_permissions(collection_id);10151016 let key_permissions = keys1017 .map(|keys| {1018 keys.into_iter()1019 .filter_map(|key| {1020 permissions1021 .get(&key)1022 .map(|permission| PropertyKeyPermission {1023 key,1024 permission: permission.clone(),1025 })1026 })1027 .collect()1028 })1029 .unwrap_or_else(|| {1030 permissions1031 .into_iter()1032 .map(|(key, permission)| PropertyKeyPermission {1033 key,1034 permission,1035 })1036 .collect()1037 });10381039 Ok(key_permissions)1040 }10411042 pub fn toggle_allowlist(1043 collection: &CollectionHandle<T>,1044 sender: &T::CrossAccountId,1045 user: &T::CrossAccountId,1046 allowed: bool,1047 ) -> DispatchResult {1048 collection.check_is_owner_or_admin(sender)?;10491050 10511052 if allowed {1053 <Allowlist<T>>::insert((collection.id, user), true);1054 } else {1055 <Allowlist<T>>::remove((collection.id, user));1056 }10571058 Ok(())1059 }10601061 pub fn toggle_admin(1062 collection: &CollectionHandle<T>,1063 sender: &T::CrossAccountId,1064 user: &T::CrossAccountId,1065 admin: bool,1066 ) -> DispatchResult {1067 collection.check_is_owner_or_admin(sender)?;10681069 let was_admin = <IsAdmin<T>>::get((collection.id, user));1070 if was_admin == admin {1071 return Ok(());1072 }1073 let amount = <AdminAmount<T>>::get(collection.id);10741075 if admin {1076 let amount = amount1077 .checked_add(1)1078 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1079 ensure!(1080 amount <= Self::collection_admins_limit(),1081 <Error<T>>::CollectionAdminCountExceeded,1082 );10831084 10851086 <AdminAmount<T>>::insert(collection.id, amount);1087 <IsAdmin<T>>::insert((collection.id, user), true);1088 } else {1089 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1090 <IsAdmin<T>>::remove((collection.id, user));1091 }10921093 Ok(())1094 }10951096 pub fn clamp_limits(1097 mode: CollectionMode,1098 old_limit: &CollectionLimits,1099 mut new_limit: CollectionLimits,1100 ) -> Result<CollectionLimits, DispatchError> {1101 limit_default!(old_limit, new_limit,1102 account_token_ownership_limit => ensure!(1103 new_limit <= MAX_TOKEN_OWNERSHIP,1104 <Error<T>>::CollectionLimitBoundsExceeded,1105 ),1106 sponsor_transfer_timeout(match mode {1107 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1108 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1109 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1110 }) => ensure!(1111 new_limit <= MAX_SPONSOR_TIMEOUT,1112 <Error<T>>::CollectionLimitBoundsExceeded,1113 ),1114 sponsored_data_size => ensure!(1115 new_limit <= CUSTOM_DATA_LIMIT,1116 <Error<T>>::CollectionLimitBoundsExceeded,1117 ),1118 token_limit => ensure!(1119 old_limit >= new_limit && new_limit > 0,1120 <Error<T>>::CollectionTokenLimitExceeded1121 ),1122 owner_can_transfer => ensure!(1123 old_limit || !new_limit,1124 <Error<T>>::OwnerPermissionsCantBeReverted,1125 ),1126 owner_can_destroy => ensure!(1127 old_limit || !new_limit,1128 <Error<T>>::OwnerPermissionsCantBeReverted,1129 ),1130 sponsored_data_rate_limit => {},1131 transfers_enabled => {},1132 );1133 Ok(new_limit)1134 }1135 pub fn clamp_permissions(1136 mode: CollectionMode,1137 old_limit: &CollectionPermissions,1138 mut new_limit: CollectionPermissions,1139 ) -> Result<CollectionPermissions, DispatchError> {1140 limit_default_clone!(old_limit, new_limit,1141 );1142 Ok(new_limit)1143 }1144}11451146#[macro_export]1147macro_rules! unsupported {1148 () => {1149 Err(<Error<T>>::UnsupportedOperation.into())1150 };1151}115211531154pub trait CommonWeightInfo<CrossAccountId> {1155 fn create_item() -> Weight;1156 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1157 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1158 fn burn_item() -> Weight;1159 fn set_collection_properties(amount: u32) -> Weight;1160 fn delete_collection_properties(amount: u32) -> Weight;1161 fn set_token_properties(amount: u32) -> Weight;1162 fn delete_token_properties(amount: u32) -> Weight;1163 fn set_property_permissions(amount: u32) -> Weight;1164 fn transfer() -> Weight;1165 fn approve() -> Weight;1166 fn transfer_from() -> Weight;1167 fn burn_from() -> Weight;1168}11691170pub trait CommonCollectionOperations<T: Config> {1171 fn create_item(1172 &self,1173 sender: T::CrossAccountId,1174 to: T::CrossAccountId,1175 data: CreateItemData,1176 nesting_budget: &dyn Budget,1177 ) -> DispatchResultWithPostInfo;1178 fn create_multiple_items(1179 &self,1180 sender: T::CrossAccountId,1181 to: T::CrossAccountId,1182 data: Vec<CreateItemData>,1183 nesting_budget: &dyn Budget,1184 ) -> DispatchResultWithPostInfo;1185 fn create_multiple_items_ex(1186 &self,1187 sender: T::CrossAccountId,1188 data: CreateItemExData<T::CrossAccountId>,1189 nesting_budget: &dyn Budget,1190 ) -> DispatchResultWithPostInfo;1191 fn burn_item(1192 &self,1193 sender: T::CrossAccountId,1194 token: TokenId,1195 amount: u128,1196 ) -> DispatchResultWithPostInfo;1197 fn set_collection_properties(1198 &self,1199 sender: T::CrossAccountId,1200 properties: Vec<Property>,1201 ) -> DispatchResultWithPostInfo;1202 fn delete_collection_properties(1203 &self,1204 sender: &T::CrossAccountId,1205 property_keys: Vec<PropertyKey>,1206 ) -> DispatchResultWithPostInfo;1207 fn set_token_properties(1208 &self,1209 sender: T::CrossAccountId,1210 token_id: TokenId,1211 property: Vec<Property>,1212 ) -> DispatchResultWithPostInfo;1213 fn delete_token_properties(1214 &self,1215 sender: T::CrossAccountId,1216 token_id: TokenId,1217 property_keys: Vec<PropertyKey>,1218 ) -> DispatchResultWithPostInfo;1219 fn set_property_permissions(1220 &self,1221 sender: &T::CrossAccountId,1222 property_permissions: Vec<PropertyKeyPermission>,1223 ) -> DispatchResultWithPostInfo;1224 fn transfer(1225 &self,1226 sender: T::CrossAccountId,1227 to: T::CrossAccountId,1228 token: TokenId,1229 amount: u128,1230 nesting_budget: &dyn Budget,1231 ) -> DispatchResultWithPostInfo;1232 fn approve(1233 &self,1234 sender: T::CrossAccountId,1235 spender: T::CrossAccountId,1236 token: TokenId,1237 amount: u128,1238 ) -> DispatchResultWithPostInfo;1239 fn transfer_from(1240 &self,1241 sender: T::CrossAccountId,1242 from: T::CrossAccountId,1243 to: T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 nesting_budget: &dyn Budget,1247 ) -> DispatchResultWithPostInfo;1248 fn burn_from(1249 &self,1250 sender: T::CrossAccountId,1251 from: T::CrossAccountId,1252 token: TokenId,1253 amount: u128,1254 nesting_budget: &dyn Budget,1255 ) -> DispatchResultWithPostInfo;12561257 fn check_nesting(1258 &self,1259 sender: T::CrossAccountId,1260 from: (CollectionId, TokenId),1261 under: TokenId,1262 budget: &dyn Budget,1263 ) -> DispatchResult;12641265 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1266 fn collection_tokens(&self) -> Vec<TokenId>;1267 fn token_exists(&self, token: TokenId) -> bool;1268 fn last_token_id(&self) -> TokenId;12691270 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1271 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1272 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1273 1274 fn total_supply(&self) -> u32;1275 1276 fn account_balance(&self, account: T::CrossAccountId) -> u32;1277 1278 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1279 fn allowance(1280 &self,1281 sender: T::CrossAccountId,1282 spender: T::CrossAccountId,1283 token: TokenId,1284 ) -> u128;1285}128612871288pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1289 let post_info = PostDispatchInfo {1290 actual_weight: Some(weight),1291 pays_fee: Pays::Yes,1292 };1293 match res {1294 Ok(()) => Ok(post_info),1295 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1296 }1297}12981299impl<T: Config> From<PropertiesError> for Error<T> {1300 fn from(error: PropertiesError) -> Self {1301 match error {1302 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1303 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1304 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1305 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1306 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1307 }1308 }1309}