1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29 transactional,30};31use pallet_evm::GasWeightMapping;32use up_data_structs::{33 COLLECTION_NUMBER_LIMIT,34 Collection,35 RpcCollection,36 CollectionId,37 CreateItemData,38 MAX_TOKEN_PREFIX_LENGTH,39 COLLECTION_ADMINS_LIMIT,40 TokenId,41 CollectionStats,42 MAX_TOKEN_OWNERSHIP,43 CollectionMode,44 NFT_SPONSOR_TRANSFER_TIMEOUT,45 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,46 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,47 MAX_SPONSOR_TIMEOUT,48 CUSTOM_DATA_LIMIT,49 CollectionLimits,50 CreateCollectionData,51 SponsorshipState,52 CreateItemExData,53 SponsoringRateLimit,54 budget::Budget,55 COLLECTION_FIELD_LIMIT,56 CollectionField,57 PhantomType,58 Property,59 Properties,60 PropertiesPermissionMap,61 PropertyKey,62 PropertyPermission,63 PropertiesError,64 PropertyKeyPermission,65 TokenData,66 TrySetProperty,67 PropertyScope,68 69 RmrkCollectionInfo,70 RmrkInstanceInfo,71 RmrkResourceInfo,72 RmrkPropertyInfo,73 RmrkBaseInfo,74 RmrkPartType,75 RmrkTheme,76 RmrkNftChild,77};7879pub use pallet::*;80use sp_core::H160;81use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};82#[cfg(feature = "runtime-benchmarks")]83pub mod benchmarking;84pub mod dispatch;85pub mod erc;86pub mod eth;8788#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]89pub struct CollectionHandle<T: Config> {90 pub id: CollectionId,91 collection: Collection<T::AccountId>,92 pub recorder: SubstrateRecorder<T>,93}94impl<T: Config> WithRecorder<T> for CollectionHandle<T> {95 fn recorder(&self) -> &SubstrateRecorder<T> {96 &self.recorder97 }98 fn into_recorder(self) -> SubstrateRecorder<T> {99 self.recorder100 }101}102impl<T: Config> CollectionHandle<T> {103 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {104 <CollectionById<T>>::get(id).map(|collection| Self {105 id,106 collection,107 recorder: SubstrateRecorder::new(gas_limit),108 })109 }110 pub fn new(id: CollectionId) -> Option<Self> {111 Self::new_with_gas_limit(id, u64::MAX)112 }113 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {114 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)115 }116 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {117 self.recorder118 .consume_gas(T::GasWeightMapping::weight_to_gas(119 <T as frame_system::Config>::DbWeight::get()120 .read121 .saturating_mul(reads),122 ))123 }124 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {125 self.recorder126 .consume_gas(T::GasWeightMapping::weight_to_gas(127 <T as frame_system::Config>::DbWeight::get()128 .write129 .saturating_mul(writes),130 ))131 }132 pub fn save(self) -> DispatchResult {133 <CollectionById<T>>::insert(self.id, self.collection);134 Ok(())135 }136}137impl<T: Config> Deref for CollectionHandle<T> {138 type Target = Collection<T::AccountId>;139140 fn deref(&self) -> &Self::Target {141 &self.collection142 }143}144145impl<T: Config> DerefMut for CollectionHandle<T> {146 fn deref_mut(&mut self) -> &mut Self::Target {147 &mut self.collection148 }149}150151impl<T: Config> CollectionHandle<T> {152 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {153 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);154 Ok(())155 }156 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {157 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))158 }159 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {160 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);161 Ok(())162 }163 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {164 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)165 }166 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {167 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)168 }169 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {170 ensure!(171 <Allowlist<T>>::get((self.id, user)),172 <Error<T>>::AddressNotInAllowlist173 );174 Ok(())175 }176}177178#[frame_support::pallet]179pub mod pallet {180 use super::*;181 use pallet_evm::account;182 use dispatch::CollectionDispatch;183 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};184 use frame_system::pallet_prelude::*;185 use frame_support::traits::Currency;186 use up_data_structs::{TokenId, mapping::TokenAddressMapping};187 use scale_info::TypeInfo;188189 #[pallet::config]190 pub trait Config:191 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config192 {193 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;194195 type Currency: Currency<Self::AccountId>;196197 #[pallet::constant]198 type CollectionCreationPrice: Get<199 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,200 >;201 type CollectionDispatch: CollectionDispatch<Self>;202203 type TreasuryAccountId: Get<Self::AccountId>;204205 type EvmTokenAddressMapping: TokenAddressMapping<H160>;206 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;207 }208209 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);210211 #[pallet::pallet]212 #[pallet::storage_version(STORAGE_VERSION)]213 #[pallet::generate_store(pub(super) trait Store)]214 pub struct Pallet<T>(_);215216 #[pallet::extra_constants]217 impl<T: Config> Pallet<T> {218 pub fn collection_admins_limit() -> u32 {219 COLLECTION_ADMINS_LIMIT220 }221 }222223 #[pallet::event]224 #[pallet::generate_deposit(pub fn deposit_event)]225 pub enum Event<T: Config> {226 227 228 229 230 231 232 233 234 235 CollectionCreated(CollectionId, u8, T::AccountId),236237 238 239 240 241 242 CollectionDestroyed(CollectionId),243244 245 246 247 248 249 250 251 252 253 254 255 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),269270 271 272 273 274 275 276 277 278 279 280 281 Transfer(282 CollectionId,283 TokenId,284 T::CrossAccountId,285 T::CrossAccountId,286 u128,287 ),288289 290 291 292 293 294 295 296 297 298 Approved(299 CollectionId,300 TokenId,301 T::CrossAccountId,302 T::CrossAccountId,303 u128,304 ),305306 CollectionPropertySet(CollectionId, PropertyKey),307308 CollectionPropertyDeleted(CollectionId, PropertyKey),309310 TokenPropertySet(CollectionId, TokenId, PropertyKey),311312 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),313314 PropertyPermissionSet(CollectionId, PropertyKey),315 }316317 #[pallet::error]318 pub enum Error<T> {319 320 CollectionNotFound,321 322 MustBeTokenOwner,323 324 NoPermission,325 326 PublicMintingNotAllowed,327 328 AddressNotInAllowlist,329330 331 CollectionNameLimitExceeded,332 333 CollectionDescriptionLimitExceeded,334 335 CollectionTokenPrefixLimitExceeded,336 337 TotalCollectionsLimitExceeded,338 339 CollectionAdminCountExceeded,340 341 CollectionLimitBoundsExceeded,342 343 OwnerPermissionsCantBeReverted,344 345 TransferNotAllowed,346 347 AccountTokenLimitExceeded,348 349 CollectionTokenLimitExceeded,350 351 MetadataFlagFrozen,352353 354 TokenNotFound,355 356 TokenValueTooLow,357 358 ApprovedValueTooLow,359 360 CantApproveMoreThanOwned,361362 363 AddressIsZero,364 365 UnsupportedOperation,366367 368 NotSufficientFounds,369370 371 NestingIsDisabled,372 373 OnlyOwnerAllowedToNest,374 375 SourceCollectionIsNotAllowedToNest,376377 378 CollectionFieldSizeExceeded,379380 381 NoSpaceForProperty,382383 384 PropertyLimitReached,385386 387 PropertyKeyIsTooLong,388389 390 InvalidCharacterInPropertyKey,391392 393 EmptyPropertyKey,394 }395396 #[pallet::storage]397 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;398 #[pallet::storage]399 pub type DestroyedCollectionCount<T> =400 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;401402 403 #[pallet::storage]404 pub type CollectionById<T> = StorageMap<405 Hasher = Blake2_128Concat,406 Key = CollectionId,407 Value = Collection<<T as frame_system::Config>::AccountId>,408 QueryKind = OptionQuery,409 >;410411 412 #[pallet::storage]413 #[pallet::getter(fn collection_properties)]414 pub type CollectionProperties<T> = StorageMap<415 Hasher = Blake2_128Concat,416 Key = CollectionId,417 Value = Properties,418 QueryKind = ValueQuery,419 OnEmpty = up_data_structs::CollectionProperties,420 >;421422 #[pallet::storage]423 #[pallet::getter(fn property_permissions)]424 pub type CollectionPropertyPermissions<T> = StorageMap<425 Hasher = Blake2_128Concat,426 Key = CollectionId,427 Value = PropertiesPermissionMap,428 QueryKind = ValueQuery,429 >;430431 432 #[pallet::storage]433 pub type CollectionData<T> = StorageNMap<434 Key = (435 Key<Twox64Concat, CollectionId>,436 Key<Twox64Concat, CollectionField>,437 ),438 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,439 QueryKind = ValueQuery,440 >;441442 #[pallet::storage]443 pub type AdminAmount<T> = StorageMap<444 Hasher = Blake2_128Concat,445 Key = CollectionId,446 Value = u32,447 QueryKind = ValueQuery,448 >;449450 451 #[pallet::storage]452 pub type IsAdmin<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 Allowlist<T: Config> = StorageNMap<464 Key = (465 Key<Blake2_128Concat, CollectionId>,466 Key<Blake2_128Concat, T::CrossAccountId>,467 ),468 Value = bool,469 QueryKind = ValueQuery,470 >;471472 473 #[pallet::storage]474 pub type DummyStorageValue<T: Config> = StorageValue<475 Value = (476 CollectionStats,477 CollectionId,478 TokenId,479 PhantomType<TokenData<T::CrossAccountId>>,480 PhantomType<RpcCollection<T::AccountId>>,481 482 PhantomType<RmrkCollectionInfo<T::AccountId>>,483 PhantomType<RmrkInstanceInfo<T::AccountId>>,484 PhantomType<RmrkResourceInfo>,485 PhantomType<RmrkPropertyInfo>,486 PhantomType<RmrkBaseInfo<T::AccountId>>,487 PhantomType<RmrkPartType>,488 PhantomType<RmrkTheme>,489 PhantomType<RmrkNftChild>,490 ),491 QueryKind = OptionQuery,492 >;493494 #[pallet::hooks]495 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {496 fn on_runtime_upgrade() -> Weight {497 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {498 use up_data_structs::{CollectionVersion1, CollectionVersion2};499 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {500 Self::set_field_raw(501 id,502 CollectionField::OffchainSchema,503 v.offchain_schema.clone().into_inner(),504 )505 .expect("data has lower bounds than field");506 Self::set_field_raw(507 id,508 CollectionField::ConstOnChainSchema,509 v.const_on_chain_schema.clone().into_inner(),510 )511 .expect("data has lower bounds than field");512513 Some(CollectionVersion2::from(v))514 });515 }516517 0518 }519 }520}521522impl<T: Config> Pallet<T> {523 524 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {525 ensure!(526 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,527 <Error<T>>::AddressIsZero528 );529 Ok(())530 }531 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {532 <IsAdmin<T>>::iter_prefix((collection,))533 .map(|(a, _)| a)534 .collect()535 }536 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {537 <Allowlist<T>>::iter_prefix((collection,))538 .map(|(a, _)| a)539 .collect()540 }541 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {542 <Allowlist<T>>::get((collection, user))543 }544 pub fn collection_stats() -> CollectionStats {545 let created = <CreatedCollectionCount<T>>::get();546 let destroyed = <DestroyedCollectionCount<T>>::get();547 CollectionStats {548 created: created.0,549 destroyed: destroyed.0,550 alive: created.0 - destroyed.0,551 }552 }553554 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {555 let collection = <CollectionById<T>>::get(collection);556 if collection.is_none() {557 return None;558 }559560 let collection = collection.unwrap();561 let limits = collection.limits;562 let effective_limits = CollectionLimits {563 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),564 sponsored_data_size: Some(limits.sponsored_data_size()),565 sponsored_data_rate_limit: Some(566 limits567 .sponsored_data_rate_limit568 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),569 ),570 token_limit: Some(limits.token_limit()),571 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(572 match collection.mode {573 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,574 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,575 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,576 },577 )),578 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),579 owner_can_transfer: Some(limits.owner_can_transfer()),580 owner_can_destroy: Some(limits.owner_can_destroy()),581 transfers_enabled: Some(limits.transfers_enabled()),582 nesting_rule: Some(limits.nesting_rule().clone()),583 };584585 Some(effective_limits)586 }587588 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {589 let Collection {590 name,591 description,592 owner,593 mode,594 access,595 token_prefix,596 mint_mode,597 schema_version,598 sponsorship,599 limits,600 } = <CollectionById<T>>::get(collection)?;601602 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)603 .iter()604 .map(|(key, permission)| PropertyKeyPermission {605 key: key.clone(),606 permission: permission.clone(),607 })608 .collect();609610 let properties = <CollectionProperties<T>>::get(collection)611 .iter()612 .map(|(key, value)| Property {613 key: key.clone(),614 value: value.clone(),615 })616 .collect();617618 Some(RpcCollection {619 name: name.into_inner(),620 description: description.into_inner(),621 owner,622 mode,623 access,624 token_prefix: token_prefix.into_inner(),625 mint_mode,626 schema_version,627 sponsorship,628 limits,629 offchain_schema: <CollectionData<T>>::get((630 collection,631 CollectionField::OffchainSchema,632 ))633 .into_inner(),634 const_on_chain_schema: <CollectionData<T>>::get((635 collection,636 CollectionField::ConstOnChainSchema,637 ))638 .into_inner(),639 token_property_permissions,640 properties,641 })642 }643}644645impl<T: Config> Pallet<T> {646 pub fn init_collection(647 owner: T::AccountId,648 data: CreateCollectionData<T::AccountId>,649 ) -> Result<CollectionId, DispatchError> {650 {651 ensure!(652 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,653 Error::<T>::CollectionTokenPrefixLimitExceeded654 );655 }656657 let created_count = <CreatedCollectionCount<T>>::get()658 .0659 .checked_add(1)660 .ok_or(ArithmeticError::Overflow)?;661 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;662 let id = CollectionId(created_count);663664 665 ensure!(666 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,667 <Error<T>>::TotalCollectionsLimitExceeded668 );669670 671672 let collection = Collection {673 owner: owner.clone(),674 name: data.name,675 mode: data.mode.clone(),676 mint_mode: false,677 access: data.access.unwrap_or_default(),678 description: data.description,679 token_prefix: data.token_prefix,680 schema_version: data.schema_version.unwrap_or_default(),681 sponsorship: data682 .pending_sponsor683 .map(SponsorshipState::Unconfirmed)684 .unwrap_or_default(),685 limits: data686 .limits687 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))688 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,689 };690691 let mut collection_properties = up_data_structs::CollectionProperties::get();692 collection_properties693 .try_set_from_iter(data.properties.into_iter())694 .map_err(<Error<T>>::from)?;695696 CollectionProperties::<T>::insert(id, collection_properties);697698 let mut token_props_permissions = PropertiesPermissionMap::new();699 token_props_permissions700 .try_set_from_iter(data.token_property_permissions.into_iter())701 .map_err(<Error<T>>::from)?;702703 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);704705 706 {707 let mut imbalance =708 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();709 imbalance.subsume(710 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(711 &T::TreasuryAccountId::get(),712 T::CollectionCreationPrice::get(),713 ),714 );715 <T as Config>::Currency::settle(716 &owner,717 imbalance,718 WithdrawReasons::TRANSFER,719 ExistenceRequirement::KeepAlive,720 )721 .map_err(|_| Error::<T>::NotSufficientFounds)?;722 }723724 <CreatedCollectionCount<T>>::put(created_count);725 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));726 <CollectionById<T>>::insert(id, collection);727 Self::set_field_raw(728 id,729 CollectionField::OffchainSchema,730 data.offchain_schema.into_inner(),731 )732 .expect("data has lower bounds than field");733 Self::set_field_raw(734 id,735 CollectionField::ConstOnChainSchema,736 data.const_on_chain_schema.into_inner(),737 )738 .expect("data has lower bounds than field");739 Ok(id)740 }741742 pub fn destroy_collection(743 collection: CollectionHandle<T>,744 sender: &T::CrossAccountId,745 ) -> DispatchResult {746 ensure!(747 collection.limits.owner_can_destroy(),748 <Error<T>>::NoPermission,749 );750 collection.check_is_owner(sender)?;751752 let destroyed_collections = <DestroyedCollectionCount<T>>::get()753 .0754 .checked_add(1)755 .ok_or(ArithmeticError::Overflow)?;756757 758759 <DestroyedCollectionCount<T>>::put(destroyed_collections);760 <CollectionById<T>>::remove(collection.id);761 <CollectionData<T>>::remove_prefix((collection.id,), None);762 <AdminAmount<T>>::remove(collection.id);763 <IsAdmin<T>>::remove_prefix((collection.id,), None);764 <Allowlist<T>>::remove_prefix((collection.id,), None);765 <CollectionProperties<T>>::remove(collection.id);766767 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));768 Ok(())769 }770771 pub fn set_collection_property(772 collection: &CollectionHandle<T>,773 sender: &T::CrossAccountId,774 property: Property,775 ) -> DispatchResult {776 collection.check_is_owner_or_admin(sender)?;777778 CollectionProperties::<T>::try_mutate(collection.id, |properties| {779 let property = property.clone();780 properties.try_set(property.key, property.value)781 })782 .map_err(<Error<T>>::from)?;783784 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));785786 Ok(())787 }788789 pub fn set_scoped_collection_property(790 collection: &CollectionHandle<T>,791 scope: PropertyScope,792 property: Property,793 ) -> DispatchResult {794 CollectionProperties::<T>::try_mutate(collection.id, |properties| {795 properties.try_scoped_set(scope, property.key, property.value)796 })797 .map_err(<Error<T>>::from)?;798799 Ok(())800 }801802 #[transactional]803 pub fn set_scoped_collection_properties(804 collection: &CollectionHandle<T>,805 scope: PropertyScope,806 properties: impl Iterator<Item=Property>,807 ) -> DispatchResult {808 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {809 stored_properties.try_scoped_set_from_iter(scope, properties)810 })811 .map_err(<Error<T>>::from)?;812813 Ok(())814 }815816 #[transactional]817 pub fn set_collection_properties(818 collection: &CollectionHandle<T>,819 sender: &T::CrossAccountId,820 properties: Vec<Property>,821 ) -> DispatchResult {822 for property in properties {823 Self::set_collection_property(collection, sender, property)?;824 }825826 Ok(())827 }828829 pub fn delete_collection_property(830 collection: &CollectionHandle<T>,831 sender: &T::CrossAccountId,832 property_key: PropertyKey,833 ) -> DispatchResult {834 collection.check_is_owner_or_admin(sender)?;835836 CollectionProperties::<T>::try_mutate(collection.id, |properties| {837 properties.remove(&property_key)838 })839 .map_err(<Error<T>>::from)?;840841 Self::deposit_event(Event::CollectionPropertyDeleted(842 collection.id,843 property_key,844 ));845846 Ok(())847 }848849 #[transactional]850 pub fn delete_collection_properties(851 collection: &CollectionHandle<T>,852 sender: &T::CrossAccountId,853 property_keys: Vec<PropertyKey>,854 ) -> DispatchResult {855 for key in property_keys {856 Self::delete_collection_property(collection, sender, key)?;857 }858859 Ok(())860 }861862 pub fn set_property_permission(863 collection: &CollectionHandle<T>,864 sender: &T::CrossAccountId,865 property_permission: PropertyKeyPermission,866 ) -> DispatchResult {867 collection.check_is_owner_or_admin(sender)?;868869 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);870 let current_permission = all_permissions.get(&property_permission.key);871 if matches![872 current_permission,873 Some(PropertyPermission { mutable: false, .. })874 ] {875 return Err(<Error<T>>::NoPermission.into());876 }877878 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {879 let property_permission = property_permission.clone();880 permissions.try_set(property_permission.key, property_permission.permission)881 })882 .map_err(<Error<T>>::from)?;883884 Self::deposit_event(Event::PropertyPermissionSet(885 collection.id,886 property_permission.key,887 ));888889 Ok(())890 }891892 #[transactional]893 pub fn set_property_permissions(894 collection: &CollectionHandle<T>,895 sender: &T::CrossAccountId,896 property_permissions: Vec<PropertyKeyPermission>,897 ) -> DispatchResult {898 for prop_pemission in property_permissions {899 Self::set_property_permission(collection, sender, prop_pemission)?;900 }901902 Ok(())903 }904905 pub fn bytes_keys_to_property_keys(906 keys: Vec<Vec<u8>>,907 ) -> Result<Vec<PropertyKey>, DispatchError> {908 keys.into_iter()909 .map(|key| -> Result<PropertyKey, DispatchError> {910 key.try_into()911 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())912 })913 .collect::<Result<Vec<PropertyKey>, DispatchError>>()914 }915916 pub fn filter_collection_properties(917 collection_id: CollectionId,918 keys: Option<Vec<PropertyKey>>,919 ) -> Result<Vec<Property>, DispatchError> {920 let properties = Self::collection_properties(collection_id);921922 let properties = keys923 .map(|keys| {924 keys.into_iter()925 .filter_map(|key| {926 properties.get(&key).map(|value| Property {927 key,928 value: value.clone(),929 })930 })931 .collect()932 })933 .unwrap_or_else(|| {934 properties935 .iter()936 .map(|(key, value)| Property {937 key: key.clone(),938 value: value.clone(),939 })940 .collect()941 });942943 Ok(properties)944 }945946 pub fn filter_property_permissions(947 collection_id: CollectionId,948 keys: Option<Vec<PropertyKey>>,949 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {950 let permissions = Self::property_permissions(collection_id);951952 let key_permissions = keys953 .map(|keys| {954 keys.into_iter()955 .filter_map(|key| {956 permissions957 .get(&key)958 .map(|permission| PropertyKeyPermission {959 key,960 permission: permission.clone(),961 })962 })963 .collect()964 })965 .unwrap_or_else(|| {966 permissions967 .iter()968 .map(|(key, permission)| PropertyKeyPermission {969 key: key.clone(),970 permission: permission.clone(),971 })972 .collect()973 });974975 Ok(key_permissions)976 }977978 fn set_field_raw(979 collection_id: CollectionId,980 field: CollectionField,981 value: Vec<u8>,982 ) -> DispatchResult {983 if !value.is_empty() {984 <CollectionData<T>>::insert(985 (collection_id, field),986 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,987 )988 } else {989 <CollectionData<T>>::remove((collection_id, field));990 }991 Ok(())992 }993994 pub fn set_field(995 collection: &CollectionHandle<T>,996 sender: &T::CrossAccountId,997 field: CollectionField,998 value: Vec<u8>,999 ) -> DispatchResult {1000 collection.check_is_owner_or_admin(sender)?;10011002 10031004 Self::set_field_raw(collection.id, field, value)1005 }10061007 pub fn toggle_allowlist(1008 collection: &CollectionHandle<T>,1009 sender: &T::CrossAccountId,1010 user: &T::CrossAccountId,1011 allowed: bool,1012 ) -> DispatchResult {1013 collection.check_is_owner_or_admin(sender)?;10141015 10161017 if allowed {1018 <Allowlist<T>>::insert((collection.id, user), true);1019 } else {1020 <Allowlist<T>>::remove((collection.id, user));1021 }10221023 Ok(())1024 }10251026 pub fn toggle_admin(1027 collection: &CollectionHandle<T>,1028 sender: &T::CrossAccountId,1029 user: &T::CrossAccountId,1030 admin: bool,1031 ) -> DispatchResult {1032 collection.check_is_owner_or_admin(sender)?;10331034 let was_admin = <IsAdmin<T>>::get((collection.id, user));1035 if was_admin == admin {1036 return Ok(());1037 }1038 let amount = <AdminAmount<T>>::get(collection.id);10391040 if admin {1041 let amount = amount1042 .checked_add(1)1043 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1044 ensure!(1045 amount <= Self::collection_admins_limit(),1046 <Error<T>>::CollectionAdminCountExceeded,1047 );10481049 10501051 <AdminAmount<T>>::insert(collection.id, amount);1052 <IsAdmin<T>>::insert((collection.id, user), true);1053 } else {1054 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1055 <IsAdmin<T>>::remove((collection.id, user));1056 }10571058 Ok(())1059 }10601061 pub fn clamp_limits(1062 mode: CollectionMode,1063 old_limit: &CollectionLimits,1064 mut new_limit: CollectionLimits,1065 ) -> Result<CollectionLimits, DispatchError> {1066 macro_rules! limit_default {1067 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1068 $(1069 if let Some($new) = $new.$field {1070 let $old = $old.$field($($arg)?);1071 let _ = $new;1072 let _ = $old;1073 $check1074 } else {1075 $new.$field = $old.$field1076 }1077 )*1078 }};1079 }10801081 limit_default!(old_limit, new_limit,1082 account_token_ownership_limit => ensure!(1083 new_limit <= MAX_TOKEN_OWNERSHIP,1084 <Error<T>>::CollectionLimitBoundsExceeded,1085 ),1086 sponsor_transfer_timeout(match mode {1087 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1088 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1089 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1090 }) => ensure!(1091 new_limit <= MAX_SPONSOR_TIMEOUT,1092 <Error<T>>::CollectionLimitBoundsExceeded,1093 ),1094 sponsored_data_size => ensure!(1095 new_limit <= CUSTOM_DATA_LIMIT,1096 <Error<T>>::CollectionLimitBoundsExceeded,1097 ),1098 token_limit => ensure!(1099 old_limit >= new_limit && new_limit > 0,1100 <Error<T>>::CollectionTokenLimitExceeded1101 ),1102 owner_can_transfer => ensure!(1103 old_limit || !new_limit,1104 <Error<T>>::OwnerPermissionsCantBeReverted,1105 ),1106 owner_can_destroy => ensure!(1107 old_limit || !new_limit,1108 <Error<T>>::OwnerPermissionsCantBeReverted,1109 ),1110 sponsored_data_rate_limit => {},1111 transfers_enabled => {},1112 );1113 Ok(new_limit)1114 }1115}11161117#[macro_export]1118macro_rules! unsupported {1119 () => {1120 Err(<Error<T>>::UnsupportedOperation.into())1121 };1122}112311241125pub trait CommonWeightInfo<CrossAccountId> {1126 fn create_item() -> Weight;1127 fn create_multiple_items(amount: u32) -> Weight;1128 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1129 fn burn_item() -> Weight;1130 fn set_collection_properties(amount: u32) -> Weight;1131 fn delete_collection_properties(amount: u32) -> Weight;1132 fn set_token_properties(amount: u32) -> Weight;1133 fn delete_token_properties(amount: u32) -> Weight;1134 fn set_property_permissions(amount: u32) -> Weight;1135 fn transfer() -> Weight;1136 fn approve() -> Weight;1137 fn transfer_from() -> Weight;1138 fn burn_from() -> Weight;1139}11401141pub trait CommonCollectionOperations<T: Config> {1142 fn create_item(1143 &self,1144 sender: T::CrossAccountId,1145 to: T::CrossAccountId,1146 data: CreateItemData,1147 nesting_budget: &dyn Budget,1148 ) -> DispatchResultWithPostInfo;1149 fn create_multiple_items(1150 &self,1151 sender: T::CrossAccountId,1152 to: T::CrossAccountId,1153 data: Vec<CreateItemData>,1154 nesting_budget: &dyn Budget,1155 ) -> DispatchResultWithPostInfo;1156 fn create_multiple_items_ex(1157 &self,1158 sender: T::CrossAccountId,1159 data: CreateItemExData<T::CrossAccountId>,1160 nesting_budget: &dyn Budget,1161 ) -> DispatchResultWithPostInfo;1162 fn burn_item(1163 &self,1164 sender: T::CrossAccountId,1165 token: TokenId,1166 amount: u128,1167 ) -> DispatchResultWithPostInfo;1168 fn set_collection_properties(1169 &self,1170 sender: T::CrossAccountId,1171 properties: Vec<Property>,1172 ) -> DispatchResultWithPostInfo;1173 fn delete_collection_properties(1174 &self,1175 sender: &T::CrossAccountId,1176 property_keys: Vec<PropertyKey>,1177 ) -> DispatchResultWithPostInfo;1178 fn set_token_properties(1179 &self,1180 sender: T::CrossAccountId,1181 token_id: TokenId,1182 property: Vec<Property>,1183 ) -> DispatchResultWithPostInfo;1184 fn delete_token_properties(1185 &self,1186 sender: T::CrossAccountId,1187 token_id: TokenId,1188 property_keys: Vec<PropertyKey>,1189 ) -> DispatchResultWithPostInfo;1190 fn set_property_permissions(1191 &self,1192 sender: &T::CrossAccountId,1193 property_permissions: Vec<PropertyKeyPermission>,1194 ) -> DispatchResultWithPostInfo;1195 fn transfer(1196 &self,1197 sender: T::CrossAccountId,1198 to: T::CrossAccountId,1199 token: TokenId,1200 amount: u128,1201 nesting_budget: &dyn Budget,1202 ) -> DispatchResultWithPostInfo;1203 fn approve(1204 &self,1205 sender: T::CrossAccountId,1206 spender: T::CrossAccountId,1207 token: TokenId,1208 amount: u128,1209 ) -> DispatchResultWithPostInfo;1210 fn transfer_from(1211 &self,1212 sender: T::CrossAccountId,1213 from: T::CrossAccountId,1214 to: T::CrossAccountId,1215 token: TokenId,1216 amount: u128,1217 nesting_budget: &dyn Budget,1218 ) -> DispatchResultWithPostInfo;1219 fn burn_from(1220 &self,1221 sender: T::CrossAccountId,1222 from: T::CrossAccountId,1223 token: TokenId,1224 amount: u128,1225 nesting_budget: &dyn Budget,1226 ) -> DispatchResultWithPostInfo;12271228 fn check_nesting(1229 &self,1230 sender: T::CrossAccountId,1231 from: (CollectionId, TokenId),1232 under: TokenId,1233 budget: &dyn Budget,1234 ) -> DispatchResult;12351236 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1237 fn collection_tokens(&self) -> Vec<TokenId>;1238 fn token_exists(&self, token: TokenId) -> bool;1239 fn last_token_id(&self) -> TokenId;12401241 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1242 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1243 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1244 1245 fn total_supply(&self) -> u32;1246 1247 fn account_balance(&self, account: T::CrossAccountId) -> u32;1248 1249 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1250 fn allowance(1251 &self,1252 sender: T::CrossAccountId,1253 spender: T::CrossAccountId,1254 token: TokenId,1255 ) -> u128;1256}125712581259pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1260 let post_info = PostDispatchInfo {1261 actual_weight: Some(weight),1262 pays_fee: Pays::Yes,1263 };1264 match res {1265 Ok(()) => Ok(post_info),1266 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1267 }1268}12691270impl<T: Config> From<PropertiesError> for Error<T> {1271 fn from(error: PropertiesError) -> Self {1272 match error {1273 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1274 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1275 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1276 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1277 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1278 }1279 }1280}