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 PropertyValue,63 PropertyPermission,64 PropertiesError,65 PropertyKeyPermission,66 TokenData,67 TrySetProperty,68 PropertyScope,69 70 RmrkCollectionInfo,71 RmrkInstanceInfo,72 RmrkResourceInfo,73 RmrkPropertyInfo,74 RmrkBaseInfo,75 RmrkPartType,76 RmrkTheme,77 RmrkNftChild,78};7980pub use pallet::*;81use sp_core::H160;82use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};83#[cfg(feature = "runtime-benchmarks")]84pub mod benchmarking;85pub mod dispatch;86pub mod erc;87pub mod eth;8889#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]90pub struct CollectionHandle<T: Config> {91 pub id: CollectionId,92 collection: Collection<T::AccountId>,93 pub recorder: SubstrateRecorder<T>,94}95impl<T: Config> WithRecorder<T> for CollectionHandle<T> {96 fn recorder(&self) -> &SubstrateRecorder<T> {97 &self.recorder98 }99 fn into_recorder(self) -> SubstrateRecorder<T> {100 self.recorder101 }102}103impl<T: Config> CollectionHandle<T> {104 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {105 <CollectionById<T>>::get(id).map(|collection| Self {106 id,107 collection,108 recorder: SubstrateRecorder::new(gas_limit),109 })110 }111 pub fn new(id: CollectionId) -> Option<Self> {112 Self::new_with_gas_limit(id, u64::MAX)113 }114 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {115 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)116 }117 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {118 self.recorder119 .consume_gas(T::GasWeightMapping::weight_to_gas(120 <T as frame_system::Config>::DbWeight::get()121 .read122 .saturating_mul(reads),123 ))124 }125 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {126 self.recorder127 .consume_gas(T::GasWeightMapping::weight_to_gas(128 <T as frame_system::Config>::DbWeight::get()129 .write130 .saturating_mul(writes),131 ))132 }133 pub fn save(self) -> DispatchResult {134 <CollectionById<T>>::insert(self.id, self.collection);135 Ok(())136 }137}138impl<T: Config> Deref for CollectionHandle<T> {139 type Target = Collection<T::AccountId>;140141 fn deref(&self) -> &Self::Target {142 &self.collection143 }144}145146impl<T: Config> DerefMut for CollectionHandle<T> {147 fn deref_mut(&mut self) -> &mut Self::Target {148 &mut self.collection149 }150}151152impl<T: Config> CollectionHandle<T> {153 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {154 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);155 Ok(())156 }157 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {158 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))159 }160 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {161 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);162 Ok(())163 }164 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {165 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)166 }167 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {168 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)169 }170 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {171 ensure!(172 <Allowlist<T>>::get((self.id, user)),173 <Error<T>>::AddressNotInAllowlist174 );175 Ok(())176 }177}178179#[frame_support::pallet]180pub mod pallet {181 use super::*;182 use pallet_evm::account;183 use dispatch::CollectionDispatch;184 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};185 use frame_system::pallet_prelude::*;186 use frame_support::traits::Currency;187 use up_data_structs::{TokenId, mapping::TokenAddressMapping};188 use scale_info::TypeInfo;189190 #[pallet::config]191 pub trait Config:192 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config193 {194 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;195196 type Currency: Currency<Self::AccountId>;197198 #[pallet::constant]199 type CollectionCreationPrice: Get<200 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,201 >;202 type CollectionDispatch: CollectionDispatch<Self>;203204 type TreasuryAccountId: Get<Self::AccountId>;205206 type EvmTokenAddressMapping: TokenAddressMapping<H160>;207 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;208 }209210 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);211212 #[pallet::pallet]213 #[pallet::storage_version(STORAGE_VERSION)]214 #[pallet::generate_store(pub(super) trait Store)]215 pub struct Pallet<T>(_);216217 #[pallet::extra_constants]218 impl<T: Config> Pallet<T> {219 pub fn collection_admins_limit() -> u32 {220 COLLECTION_ADMINS_LIMIT221 }222 }223224 #[pallet::event]225 #[pallet::generate_deposit(pub fn deposit_event)]226 pub enum Event<T: Config> {227 228 229 230 231 232 233 234 235 236 CollectionCreated(CollectionId, u8, T::AccountId),237238 239 240 241 242 243 CollectionDestroyed(CollectionId),244245 246 247 248 249 250 251 252 253 254 255 256 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),257258 259 260 261 262 263 264 265 266 267 268 269 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),270271 272 273 274 275 276 277 278 279 280 281 282 Transfer(283 CollectionId,284 TokenId,285 T::CrossAccountId,286 T::CrossAccountId,287 u128,288 ),289290 291 292 293 294 295 296 297 298 299 Approved(300 CollectionId,301 TokenId,302 T::CrossAccountId,303 T::CrossAccountId,304 u128,305 ),306307 CollectionPropertySet(CollectionId, PropertyKey),308309 CollectionPropertyDeleted(CollectionId, PropertyKey),310311 TokenPropertySet(CollectionId, TokenId, PropertyKey),312313 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),314315 PropertyPermissionSet(CollectionId, PropertyKey),316 }317318 #[pallet::error]319 pub enum Error<T> {320 321 CollectionNotFound,322 323 MustBeTokenOwner,324 325 NoPermission,326 327 PublicMintingNotAllowed,328 329 AddressNotInAllowlist,330331 332 CollectionNameLimitExceeded,333 334 CollectionDescriptionLimitExceeded,335 336 CollectionTokenPrefixLimitExceeded,337 338 TotalCollectionsLimitExceeded,339 340 CollectionAdminCountExceeded,341 342 CollectionLimitBoundsExceeded,343 344 OwnerPermissionsCantBeReverted,345 346 TransferNotAllowed,347 348 AccountTokenLimitExceeded,349 350 CollectionTokenLimitExceeded,351 352 MetadataFlagFrozen,353354 355 TokenNotFound,356 357 TokenValueTooLow,358 359 ApprovedValueTooLow,360 361 CantApproveMoreThanOwned,362363 364 AddressIsZero,365 366 UnsupportedOperation,367368 369 NotSufficientFounds,370371 372 NestingIsDisabled,373 374 OnlyOwnerAllowedToNest,375 376 SourceCollectionIsNotAllowedToNest,377378 379 CollectionFieldSizeExceeded,380381 382 NoSpaceForProperty,383384 385 PropertyLimitReached,386387 388 PropertyKeyIsTooLong,389390 391 InvalidCharacterInPropertyKey,392393 394 EmptyPropertyKey,395 }396397 #[pallet::storage]398 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;399 #[pallet::storage]400 pub type DestroyedCollectionCount<T> =401 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;402403 404 #[pallet::storage]405 pub type CollectionById<T> = StorageMap<406 Hasher = Blake2_128Concat,407 Key = CollectionId,408 Value = Collection<<T as frame_system::Config>::AccountId>,409 QueryKind = OptionQuery,410 >;411412 413 #[pallet::storage]414 #[pallet::getter(fn collection_properties)]415 pub type CollectionProperties<T> = StorageMap<416 Hasher = Blake2_128Concat,417 Key = CollectionId,418 Value = Properties,419 QueryKind = ValueQuery,420 OnEmpty = up_data_structs::CollectionProperties,421 >;422423 #[pallet::storage]424 #[pallet::getter(fn property_permissions)]425 pub type CollectionPropertyPermissions<T> = StorageMap<426 Hasher = Blake2_128Concat,427 Key = CollectionId,428 Value = PropertiesPermissionMap,429 QueryKind = ValueQuery,430 >;431432 433 #[pallet::storage]434 pub type CollectionData<T> = StorageNMap<435 Key = (436 Key<Twox64Concat, CollectionId>,437 Key<Twox64Concat, CollectionField>,438 ),439 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,440 QueryKind = ValueQuery,441 >;442443 #[pallet::storage]444 pub type AdminAmount<T> = StorageMap<445 Hasher = Blake2_128Concat,446 Key = CollectionId,447 Value = u32,448 QueryKind = ValueQuery,449 >;450451 452 #[pallet::storage]453 pub type IsAdmin<T: Config> = StorageNMap<454 Key = (455 Key<Blake2_128Concat, CollectionId>,456 Key<Blake2_128Concat, T::CrossAccountId>,457 ),458 Value = bool,459 QueryKind = ValueQuery,460 >;461462 463 #[pallet::storage]464 pub type Allowlist<T: Config> = StorageNMap<465 Key = (466 Key<Blake2_128Concat, CollectionId>,467 Key<Blake2_128Concat, T::CrossAccountId>,468 ),469 Value = bool,470 QueryKind = ValueQuery,471 >;472473 474 #[pallet::storage]475 pub type DummyStorageValue<T: Config> = StorageValue<476 Value = (477 CollectionStats,478 CollectionId,479 TokenId,480 PhantomType<TokenData<T::CrossAccountId>>,481 PhantomType<RpcCollection<T::AccountId>>,482 483 PhantomType<RmrkCollectionInfo<T::AccountId>>,484 PhantomType<RmrkInstanceInfo<T::AccountId>>,485 PhantomType<RmrkResourceInfo>,486 PhantomType<RmrkPropertyInfo>,487 PhantomType<RmrkBaseInfo<T::AccountId>>,488 PhantomType<RmrkPartType>,489 PhantomType<RmrkTheme>,490 PhantomType<RmrkNftChild>,491 ),492 QueryKind = OptionQuery,493 >;494495 #[pallet::hooks]496 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {497 fn on_runtime_upgrade() -> Weight {498 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {499 use up_data_structs::{CollectionVersion1, CollectionVersion2};500 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {501 Self::set_field_raw(502 id,503 CollectionField::OffchainSchema,504 v.offchain_schema.clone().into_inner(),505 )506 .expect("data has lower bounds than field");507 Self::set_field_raw(508 id,509 CollectionField::ConstOnChainSchema,510 v.const_on_chain_schema.clone().into_inner(),511 )512 .expect("data has lower bounds than field");513514 Some(CollectionVersion2::from(v))515 });516 }517518 0519 }520 }521}522523impl<T: Config> Pallet<T> {524 525 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {526 ensure!(527 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,528 <Error<T>>::AddressIsZero529 );530 Ok(())531 }532 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {533 <IsAdmin<T>>::iter_prefix((collection,))534 .map(|(a, _)| a)535 .collect()536 }537 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {538 <Allowlist<T>>::iter_prefix((collection,))539 .map(|(a, _)| a)540 .collect()541 }542 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {543 <Allowlist<T>>::get((collection, user))544 }545 pub fn collection_stats() -> CollectionStats {546 let created = <CreatedCollectionCount<T>>::get();547 let destroyed = <DestroyedCollectionCount<T>>::get();548 CollectionStats {549 created: created.0,550 destroyed: destroyed.0,551 alive: created.0 - destroyed.0,552 }553 }554555 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {556 let collection = <CollectionById<T>>::get(collection);557 if collection.is_none() {558 return None;559 }560561 let collection = collection.unwrap();562 let limits = collection.limits;563 let effective_limits = CollectionLimits {564 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),565 sponsored_data_size: Some(limits.sponsored_data_size()),566 sponsored_data_rate_limit: Some(567 limits568 .sponsored_data_rate_limit569 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),570 ),571 token_limit: Some(limits.token_limit()),572 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(573 match collection.mode {574 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,575 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,576 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,577 },578 )),579 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),580 owner_can_transfer: Some(limits.owner_can_transfer()),581 owner_can_destroy: Some(limits.owner_can_destroy()),582 transfers_enabled: Some(limits.transfers_enabled()),583 nesting_rule: Some(limits.nesting_rule().clone()),584 };585586 Some(effective_limits)587 }588589 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {590 let Collection {591 name,592 description,593 owner,594 mode,595 access,596 token_prefix,597 mint_mode,598 schema_version,599 sponsorship,600 limits,601 } = <CollectionById<T>>::get(collection)?;602603 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)604 .iter()605 .map(|(key, permission)| PropertyKeyPermission {606 key: key.clone(),607 permission: permission.clone(),608 })609 .collect();610611 let properties = <CollectionProperties<T>>::get(collection)612 .iter()613 .map(|(key, value)| Property {614 key: key.clone(),615 value: value.clone(),616 })617 .collect();618619 Some(RpcCollection {620 name: name.into_inner(),621 description: description.into_inner(),622 owner,623 mode,624 access,625 token_prefix: token_prefix.into_inner(),626 mint_mode,627 schema_version,628 sponsorship,629 limits,630 offchain_schema: <CollectionData<T>>::get((631 collection,632 CollectionField::OffchainSchema,633 ))634 .into_inner(),635 const_on_chain_schema: <CollectionData<T>>::get((636 collection,637 CollectionField::ConstOnChainSchema,638 ))639 .into_inner(),640 token_property_permissions,641 properties,642 })643 }644}645646impl<T: Config> Pallet<T> {647 pub fn init_collection(648 owner: T::AccountId,649 data: CreateCollectionData<T::AccountId>,650 ) -> Result<CollectionId, DispatchError> {651 {652 ensure!(653 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,654 Error::<T>::CollectionTokenPrefixLimitExceeded655 );656 }657658 let created_count = <CreatedCollectionCount<T>>::get()659 .0660 .checked_add(1)661 .ok_or(ArithmeticError::Overflow)?;662 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;663 let id = CollectionId(created_count);664665 666 ensure!(667 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,668 <Error<T>>::TotalCollectionsLimitExceeded669 );670671 672673 let collection = Collection {674 owner: owner.clone(),675 name: data.name,676 mode: data.mode.clone(),677 mint_mode: false,678 access: data.access.unwrap_or_default(),679 description: data.description,680 token_prefix: data.token_prefix,681 schema_version: data.schema_version.unwrap_or_default(),682 sponsorship: data683 .pending_sponsor684 .map(SponsorshipState::Unconfirmed)685 .unwrap_or_default(),686 limits: data687 .limits688 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))689 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,690 };691692 let mut collection_properties = up_data_structs::CollectionProperties::get();693 collection_properties694 .try_set_from_iter(data.properties.into_iter())695 .map_err(<Error<T>>::from)?;696697 CollectionProperties::<T>::insert(id, collection_properties);698699 let mut token_props_permissions = PropertiesPermissionMap::new();700 token_props_permissions701 .try_set_from_iter(data.token_property_permissions.into_iter())702 .map_err(<Error<T>>::from)?;703704 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);705706 707 {708 let mut imbalance =709 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();710 imbalance.subsume(711 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(712 &T::TreasuryAccountId::get(),713 T::CollectionCreationPrice::get(),714 ),715 );716 <T as Config>::Currency::settle(717 &owner,718 imbalance,719 WithdrawReasons::TRANSFER,720 ExistenceRequirement::KeepAlive,721 )722 .map_err(|_| Error::<T>::NotSufficientFounds)?;723 }724725 <CreatedCollectionCount<T>>::put(created_count);726 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));727 <CollectionById<T>>::insert(id, collection);728 Self::set_field_raw(729 id,730 CollectionField::OffchainSchema,731 data.offchain_schema.into_inner(),732 )733 .expect("data has lower bounds than field");734 Self::set_field_raw(735 id,736 CollectionField::ConstOnChainSchema,737 data.const_on_chain_schema.into_inner(),738 )739 .expect("data has lower bounds than field");740 Ok(id)741 }742743 pub fn destroy_collection(744 collection: CollectionHandle<T>,745 sender: &T::CrossAccountId,746 ) -> DispatchResult {747 ensure!(748 collection.limits.owner_can_destroy(),749 <Error<T>>::NoPermission,750 );751 collection.check_is_owner(sender)?;752753 let destroyed_collections = <DestroyedCollectionCount<T>>::get()754 .0755 .checked_add(1)756 .ok_or(ArithmeticError::Overflow)?;757758 759760 <DestroyedCollectionCount<T>>::put(destroyed_collections);761 <CollectionById<T>>::remove(collection.id);762 <CollectionData<T>>::remove_prefix((collection.id,), None);763 <AdminAmount<T>>::remove(collection.id);764 <IsAdmin<T>>::remove_prefix((collection.id,), None);765 <Allowlist<T>>::remove_prefix((collection.id,), None);766 <CollectionProperties<T>>::remove(collection.id);767768 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));769 Ok(())770 }771772 pub fn set_collection_property(773 collection: &CollectionHandle<T>,774 sender: &T::CrossAccountId,775 property: Property,776 ) -> DispatchResult {777 collection.check_is_owner_or_admin(sender)?;778779 CollectionProperties::<T>::try_mutate(collection.id, |properties| {780 let property = property.clone();781 properties.try_set(property.key, property.value)782 })783 .map_err(<Error<T>>::from)?;784785 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));786787 Ok(())788 }789790 pub fn set_scoped_collection_property(791 collection: &CollectionHandle<T>,792 scope: PropertyScope,793 property: Property,794 ) -> DispatchResult {795 CollectionProperties::<T>::try_mutate(collection.id, |properties| {796 properties.try_scoped_set(scope, property.key, property.value)797 })798 .map_err(<Error<T>>::from)?;799800 Ok(())801 }802803 #[transactional]804 pub fn set_scoped_collection_properties(805 collection: &CollectionHandle<T>,806 scope: PropertyScope,807 properties: impl Iterator<Item=Property>,808 ) -> DispatchResult {809 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {810 stored_properties.try_scoped_set_from_iter(scope, properties)811 })812 .map_err(<Error<T>>::from)?;813814 Ok(())815 }816817 #[transactional]818 pub fn set_collection_properties(819 collection: &CollectionHandle<T>,820 sender: &T::CrossAccountId,821 properties: Vec<Property>,822 ) -> DispatchResult {823 for property in properties {824 Self::set_collection_property(collection, sender, property)?;825 }826827 Ok(())828 }829830 pub fn delete_collection_property(831 collection: &CollectionHandle<T>,832 sender: &T::CrossAccountId,833 property_key: PropertyKey,834 ) -> DispatchResult {835 collection.check_is_owner_or_admin(sender)?;836837 CollectionProperties::<T>::try_mutate(collection.id, |properties| {838 properties.remove(&property_key)839 })840 .map_err(<Error<T>>::from)?;841842 Self::deposit_event(Event::CollectionPropertyDeleted(843 collection.id,844 property_key,845 ));846847 Ok(())848 }849850 #[transactional]851 pub fn delete_collection_properties(852 collection: &CollectionHandle<T>,853 sender: &T::CrossAccountId,854 property_keys: Vec<PropertyKey>,855 ) -> DispatchResult {856 for key in property_keys {857 Self::delete_collection_property(collection, sender, key)?;858 }859860 Ok(())861 }862863 pub fn set_property_permission(864 collection: &CollectionHandle<T>,865 sender: &T::CrossAccountId,866 property_permission: PropertyKeyPermission,867 ) -> DispatchResult {868 collection.check_is_owner_or_admin(sender)?;869870 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);871 let current_permission = all_permissions.get(&property_permission.key);872 if matches![873 current_permission,874 Some(PropertyPermission { mutable: false, .. })875 ] {876 return Err(<Error<T>>::NoPermission.into());877 }878879 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {880 let property_permission = property_permission.clone();881 permissions.try_set(property_permission.key, property_permission.permission)882 })883 .map_err(<Error<T>>::from)?;884885 Self::deposit_event(Event::PropertyPermissionSet(886 collection.id,887 property_permission.key,888 ));889890 Ok(())891 }892893 #[transactional]894 pub fn set_property_permissions(895 collection: &CollectionHandle<T>,896 sender: &T::CrossAccountId,897 property_permissions: Vec<PropertyKeyPermission>,898 ) -> DispatchResult {899 for prop_pemission in property_permissions {900 Self::set_property_permission(collection, sender, prop_pemission)?;901 }902903 Ok(())904 }905906 pub fn get_collection_property(collection_id: CollectionId, key: &PropertyKey) -> Option<PropertyValue> {907 Self::collection_properties(collection_id)908 .get(key)909 .cloned()910 }911912 pub fn bytes_keys_to_property_keys(913 keys: Vec<Vec<u8>>,914 ) -> Result<Vec<PropertyKey>, DispatchError> {915 keys.into_iter()916 .map(|key| -> Result<PropertyKey, DispatchError> {917 key.try_into()918 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())919 })920 .collect::<Result<Vec<PropertyKey>, DispatchError>>()921 }922923 pub fn filter_collection_properties(924 collection_id: CollectionId,925 keys: Option<Vec<PropertyKey>>,926 ) -> Result<Vec<Property>, DispatchError> {927 let properties = Self::collection_properties(collection_id);928929 let properties = keys930 .map(|keys| {931 keys.into_iter()932 .filter_map(|key| {933 properties.get(&key).map(|value| Property {934 key,935 value: value.clone(),936 })937 })938 .collect()939 })940 .unwrap_or_else(|| {941 properties942 .iter()943 .map(|(key, value)| Property {944 key: key.clone(),945 value: value.clone(),946 })947 .collect()948 });949950 Ok(properties)951 }952953 pub fn filter_property_permissions(954 collection_id: CollectionId,955 keys: Option<Vec<PropertyKey>>,956 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {957 let permissions = Self::property_permissions(collection_id);958959 let key_permissions = keys960 .map(|keys| {961 keys.into_iter()962 .filter_map(|key| {963 permissions964 .get(&key)965 .map(|permission| PropertyKeyPermission {966 key,967 permission: permission.clone(),968 })969 })970 .collect()971 })972 .unwrap_or_else(|| {973 permissions974 .iter()975 .map(|(key, permission)| PropertyKeyPermission {976 key: key.clone(),977 permission: permission.clone(),978 })979 .collect()980 });981982 Ok(key_permissions)983 }984985 fn set_field_raw(986 collection_id: CollectionId,987 field: CollectionField,988 value: Vec<u8>,989 ) -> DispatchResult {990 if !value.is_empty() {991 <CollectionData<T>>::insert(992 (collection_id, field),993 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,994 )995 } else {996 <CollectionData<T>>::remove((collection_id, field));997 }998 Ok(())999 }10001001 pub fn set_field(1002 collection: &CollectionHandle<T>,1003 sender: &T::CrossAccountId,1004 field: CollectionField,1005 value: Vec<u8>,1006 ) -> DispatchResult {1007 collection.check_is_owner_or_admin(sender)?;10081009 10101011 Self::set_field_raw(collection.id, field, value)1012 }10131014 pub fn toggle_allowlist(1015 collection: &CollectionHandle<T>,1016 sender: &T::CrossAccountId,1017 user: &T::CrossAccountId,1018 allowed: bool,1019 ) -> DispatchResult {1020 collection.check_is_owner_or_admin(sender)?;10211022 10231024 if allowed {1025 <Allowlist<T>>::insert((collection.id, user), true);1026 } else {1027 <Allowlist<T>>::remove((collection.id, user));1028 }10291030 Ok(())1031 }10321033 pub fn toggle_admin(1034 collection: &CollectionHandle<T>,1035 sender: &T::CrossAccountId,1036 user: &T::CrossAccountId,1037 admin: bool,1038 ) -> DispatchResult {1039 collection.check_is_owner_or_admin(sender)?;10401041 let was_admin = <IsAdmin<T>>::get((collection.id, user));1042 if was_admin == admin {1043 return Ok(());1044 }1045 let amount = <AdminAmount<T>>::get(collection.id);10461047 if admin {1048 let amount = amount1049 .checked_add(1)1050 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1051 ensure!(1052 amount <= Self::collection_admins_limit(),1053 <Error<T>>::CollectionAdminCountExceeded,1054 );10551056 10571058 <AdminAmount<T>>::insert(collection.id, amount);1059 <IsAdmin<T>>::insert((collection.id, user), true);1060 } else {1061 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1062 <IsAdmin<T>>::remove((collection.id, user));1063 }10641065 Ok(())1066 }10671068 pub fn clamp_limits(1069 mode: CollectionMode,1070 old_limit: &CollectionLimits,1071 mut new_limit: CollectionLimits,1072 ) -> Result<CollectionLimits, DispatchError> {1073 macro_rules! limit_default {1074 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1075 $(1076 if let Some($new) = $new.$field {1077 let $old = $old.$field($($arg)?);1078 let _ = $new;1079 let _ = $old;1080 $check1081 } else {1082 $new.$field = $old.$field1083 }1084 )*1085 }};1086 }10871088 limit_default!(old_limit, new_limit,1089 account_token_ownership_limit => ensure!(1090 new_limit <= MAX_TOKEN_OWNERSHIP,1091 <Error<T>>::CollectionLimitBoundsExceeded,1092 ),1093 sponsor_transfer_timeout(match mode {1094 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1095 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1096 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1097 }) => ensure!(1098 new_limit <= MAX_SPONSOR_TIMEOUT,1099 <Error<T>>::CollectionLimitBoundsExceeded,1100 ),1101 sponsored_data_size => ensure!(1102 new_limit <= CUSTOM_DATA_LIMIT,1103 <Error<T>>::CollectionLimitBoundsExceeded,1104 ),1105 token_limit => ensure!(1106 old_limit >= new_limit && new_limit > 0,1107 <Error<T>>::CollectionTokenLimitExceeded1108 ),1109 owner_can_transfer => ensure!(1110 old_limit || !new_limit,1111 <Error<T>>::OwnerPermissionsCantBeReverted,1112 ),1113 owner_can_destroy => ensure!(1114 old_limit || !new_limit,1115 <Error<T>>::OwnerPermissionsCantBeReverted,1116 ),1117 sponsored_data_rate_limit => {},1118 transfers_enabled => {},1119 );1120 Ok(new_limit)1121 }1122}11231124#[macro_export]1125macro_rules! unsupported {1126 () => {1127 Err(<Error<T>>::UnsupportedOperation.into())1128 };1129}113011311132pub trait CommonWeightInfo<CrossAccountId> {1133 fn create_item() -> Weight;1134 fn create_multiple_items(amount: u32) -> Weight;1135 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1136 fn burn_item() -> Weight;1137 fn set_collection_properties(amount: u32) -> Weight;1138 fn delete_collection_properties(amount: u32) -> Weight;1139 fn set_token_properties(amount: u32) -> Weight;1140 fn delete_token_properties(amount: u32) -> Weight;1141 fn set_property_permissions(amount: u32) -> Weight;1142 fn transfer() -> Weight;1143 fn approve() -> Weight;1144 fn transfer_from() -> Weight;1145 fn burn_from() -> Weight;1146}11471148pub trait CommonCollectionOperations<T: Config> {1149 fn create_item(1150 &self,1151 sender: T::CrossAccountId,1152 to: T::CrossAccountId,1153 data: CreateItemData,1154 nesting_budget: &dyn Budget,1155 ) -> DispatchResultWithPostInfo;1156 fn create_multiple_items(1157 &self,1158 sender: T::CrossAccountId,1159 to: T::CrossAccountId,1160 data: Vec<CreateItemData>,1161 nesting_budget: &dyn Budget,1162 ) -> DispatchResultWithPostInfo;1163 fn create_multiple_items_ex(1164 &self,1165 sender: T::CrossAccountId,1166 data: CreateItemExData<T::CrossAccountId>,1167 nesting_budget: &dyn Budget,1168 ) -> DispatchResultWithPostInfo;1169 fn burn_item(1170 &self,1171 sender: T::CrossAccountId,1172 token: TokenId,1173 amount: u128,1174 ) -> DispatchResultWithPostInfo;1175 fn set_collection_properties(1176 &self,1177 sender: T::CrossAccountId,1178 properties: Vec<Property>,1179 ) -> DispatchResultWithPostInfo;1180 fn delete_collection_properties(1181 &self,1182 sender: &T::CrossAccountId,1183 property_keys: Vec<PropertyKey>,1184 ) -> DispatchResultWithPostInfo;1185 fn set_token_properties(1186 &self,1187 sender: T::CrossAccountId,1188 token_id: TokenId,1189 property: Vec<Property>,1190 ) -> DispatchResultWithPostInfo;1191 fn delete_token_properties(1192 &self,1193 sender: T::CrossAccountId,1194 token_id: TokenId,1195 property_keys: Vec<PropertyKey>,1196 ) -> DispatchResultWithPostInfo;1197 fn set_property_permissions(1198 &self,1199 sender: &T::CrossAccountId,1200 property_permissions: Vec<PropertyKeyPermission>,1201 ) -> DispatchResultWithPostInfo;1202 fn transfer(1203 &self,1204 sender: T::CrossAccountId,1205 to: T::CrossAccountId,1206 token: TokenId,1207 amount: u128,1208 nesting_budget: &dyn Budget,1209 ) -> DispatchResultWithPostInfo;1210 fn approve(1211 &self,1212 sender: T::CrossAccountId,1213 spender: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 ) -> DispatchResultWithPostInfo;1217 fn transfer_from(1218 &self,1219 sender: T::CrossAccountId,1220 from: T::CrossAccountId,1221 to: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 nesting_budget: &dyn Budget,1225 ) -> DispatchResultWithPostInfo;1226 fn burn_from(1227 &self,1228 sender: T::CrossAccountId,1229 from: T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;12341235 fn check_nesting(1236 &self,1237 sender: T::CrossAccountId,1238 from: (CollectionId, TokenId),1239 under: TokenId,1240 budget: &dyn Budget,1241 ) -> DispatchResult;12421243 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1244 fn collection_tokens(&self) -> Vec<TokenId>;1245 fn token_exists(&self, token: TokenId) -> bool;1246 fn last_token_id(&self) -> TokenId;12471248 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1249 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1250 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1251 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1252 1253 fn total_supply(&self) -> u32;1254 1255 fn account_balance(&self, account: T::CrossAccountId) -> u32;1256 1257 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1258 fn allowance(1259 &self,1260 sender: T::CrossAccountId,1261 spender: T::CrossAccountId,1262 token: TokenId,1263 ) -> u128;1264}126512661267pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1268 let post_info = PostDispatchInfo {1269 actual_weight: Some(weight),1270 pays_fee: Pays::Yes,1271 };1272 match res {1273 Ok(()) => Ok(post_info),1274 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1275 }1276}12771278impl<T: Config> From<PropertiesError> for Error<T> {1279 fn from(error: PropertiesError) -> Self {1280 match error {1281 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1282 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1283 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1284 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1285 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1286 }1287 }1288}