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);765766 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));767 Ok(())768 }769770 pub fn set_collection_property(771 collection: &CollectionHandle<T>,772 sender: &T::CrossAccountId,773 property: Property,774 ) -> DispatchResult {775 collection.check_is_owner_or_admin(sender)?;776777 CollectionProperties::<T>::try_mutate(collection.id, |properties| {778 let property = property.clone();779 properties.try_set(property.key, property.value)780 })781 .map_err(<Error<T>>::from)?;782783 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));784785 Ok(())786 }787788 pub fn set_scoped_collection_property(789 collection: &CollectionHandle<T>,790 scope: PropertyScope,791 property: Property,792 ) -> DispatchResult {793 CollectionProperties::<T>::try_mutate(collection.id, |properties| {794 properties.try_scoped_set(scope, property.key, property.value)795 })796 .map_err(<Error<T>>::from)?;797798 Ok(())799 }800801 #[transactional]802 pub fn set_scoped_collection_properties(803 collection: &CollectionHandle<T>,804 scope: PropertyScope,805 properties: impl Iterator<Item=Property>,806 ) -> DispatchResult {807 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {808 stored_properties.try_scoped_set_from_iter(scope, properties)809 })810 .map_err(<Error<T>>::from)?;811812 Ok(())813 }814815 #[transactional]816 pub fn set_collection_properties(817 collection: &CollectionHandle<T>,818 sender: &T::CrossAccountId,819 properties: Vec<Property>,820 ) -> DispatchResult {821 for property in properties {822 Self::set_collection_property(collection, sender, property)?;823 }824825 Ok(())826 }827828 pub fn delete_collection_property(829 collection: &CollectionHandle<T>,830 sender: &T::CrossAccountId,831 property_key: PropertyKey,832 ) -> DispatchResult {833 collection.check_is_owner_or_admin(sender)?;834835 CollectionProperties::<T>::try_mutate(collection.id, |properties| {836 properties.remove(&property_key)837 })838 .map_err(<Error<T>>::from)?;839840 Self::deposit_event(Event::CollectionPropertyDeleted(841 collection.id,842 property_key,843 ));844845 Ok(())846 }847848 #[transactional]849 pub fn delete_collection_properties(850 collection: &CollectionHandle<T>,851 sender: &T::CrossAccountId,852 property_keys: Vec<PropertyKey>,853 ) -> DispatchResult {854 for key in property_keys {855 Self::delete_collection_property(collection, sender, key)?;856 }857858 Ok(())859 }860861 pub fn set_property_permission(862 collection: &CollectionHandle<T>,863 sender: &T::CrossAccountId,864 property_permission: PropertyKeyPermission,865 ) -> DispatchResult {866 collection.check_is_owner_or_admin(sender)?;867868 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);869 let current_permission = all_permissions.get(&property_permission.key);870 if matches![871 current_permission,872 Some(PropertyPermission { mutable: false, .. })873 ] {874 return Err(<Error<T>>::NoPermission.into());875 }876877 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {878 let property_permission = property_permission.clone();879 permissions.try_set(property_permission.key, property_permission.permission)880 })881 .map_err(<Error<T>>::from)?;882883 Self::deposit_event(Event::PropertyPermissionSet(884 collection.id,885 property_permission.key,886 ));887888 Ok(())889 }890891 #[transactional]892 pub fn set_property_permissions(893 collection: &CollectionHandle<T>,894 sender: &T::CrossAccountId,895 property_permissions: Vec<PropertyKeyPermission>,896 ) -> DispatchResult {897 for prop_pemission in property_permissions {898 Self::set_property_permission(collection, sender, prop_pemission)?;899 }900901 Ok(())902 }903904 pub fn bytes_keys_to_property_keys(905 keys: Vec<Vec<u8>>,906 ) -> Result<Vec<PropertyKey>, DispatchError> {907 keys.into_iter()908 .map(|key| -> Result<PropertyKey, DispatchError> {909 key.try_into()910 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())911 })912 .collect::<Result<Vec<PropertyKey>, DispatchError>>()913 }914915 pub fn filter_collection_properties(916 collection_id: CollectionId,917 keys: Option<Vec<PropertyKey>>,918 ) -> Result<Vec<Property>, DispatchError> {919 let properties = Self::collection_properties(collection_id);920921 let properties = keys922 .map(|keys| {923 keys.into_iter()924 .filter_map(|key| {925 properties.get(&key).map(|value| Property {926 key,927 value: value.clone(),928 })929 })930 .collect()931 })932 .unwrap_or_else(|| {933 properties934 .iter()935 .map(|(key, value)| Property {936 key: key.clone(),937 value: value.clone(),938 })939 .collect()940 });941942 Ok(properties)943 }944945 pub fn filter_property_permissions(946 collection_id: CollectionId,947 keys: Option<Vec<PropertyKey>>,948 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {949 let permissions = Self::property_permissions(collection_id);950951 let key_permissions = keys952 .map(|keys| {953 keys.into_iter()954 .filter_map(|key| {955 permissions956 .get(&key)957 .map(|permission| PropertyKeyPermission {958 key,959 permission: permission.clone(),960 })961 })962 .collect()963 })964 .unwrap_or_else(|| {965 permissions966 .iter()967 .map(|(key, permission)| PropertyKeyPermission {968 key: key.clone(),969 permission: permission.clone(),970 })971 .collect()972 });973974 Ok(key_permissions)975 }976977 fn set_field_raw(978 collection_id: CollectionId,979 field: CollectionField,980 value: Vec<u8>,981 ) -> DispatchResult {982 if !value.is_empty() {983 <CollectionData<T>>::insert(984 (collection_id, field),985 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,986 )987 } else {988 <CollectionData<T>>::remove((collection_id, field));989 }990 Ok(())991 }992993 pub fn set_field(994 collection: &CollectionHandle<T>,995 sender: &T::CrossAccountId,996 field: CollectionField,997 value: Vec<u8>,998 ) -> DispatchResult {999 collection.check_is_owner_or_admin(sender)?;10001001 10021003 Self::set_field_raw(collection.id, field, value)1004 }10051006 pub fn toggle_allowlist(1007 collection: &CollectionHandle<T>,1008 sender: &T::CrossAccountId,1009 user: &T::CrossAccountId,1010 allowed: bool,1011 ) -> DispatchResult {1012 collection.check_is_owner_or_admin(sender)?;10131014 10151016 if allowed {1017 <Allowlist<T>>::insert((collection.id, user), true);1018 } else {1019 <Allowlist<T>>::remove((collection.id, user));1020 }10211022 Ok(())1023 }10241025 pub fn toggle_admin(1026 collection: &CollectionHandle<T>,1027 sender: &T::CrossAccountId,1028 user: &T::CrossAccountId,1029 admin: bool,1030 ) -> DispatchResult {1031 collection.check_is_owner_or_admin(sender)?;10321033 let was_admin = <IsAdmin<T>>::get((collection.id, user));1034 if was_admin == admin {1035 return Ok(());1036 }1037 let amount = <AdminAmount<T>>::get(collection.id);10381039 if admin {1040 let amount = amount1041 .checked_add(1)1042 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1043 ensure!(1044 amount <= Self::collection_admins_limit(),1045 <Error<T>>::CollectionAdminCountExceeded,1046 );10471048 10491050 <AdminAmount<T>>::insert(collection.id, amount);1051 <IsAdmin<T>>::insert((collection.id, user), true);1052 } else {1053 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1054 <IsAdmin<T>>::remove((collection.id, user));1055 }10561057 Ok(())1058 }10591060 pub fn clamp_limits(1061 mode: CollectionMode,1062 old_limit: &CollectionLimits,1063 mut new_limit: CollectionLimits,1064 ) -> Result<CollectionLimits, DispatchError> {1065 macro_rules! limit_default {1066 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1067 $(1068 if let Some($new) = $new.$field {1069 let $old = $old.$field($($arg)?);1070 let _ = $new;1071 let _ = $old;1072 $check1073 } else {1074 $new.$field = $old.$field1075 }1076 )*1077 }};1078 }10791080 limit_default!(old_limit, new_limit,1081 account_token_ownership_limit => ensure!(1082 new_limit <= MAX_TOKEN_OWNERSHIP,1083 <Error<T>>::CollectionLimitBoundsExceeded,1084 ),1085 sponsor_transfer_timeout(match mode {1086 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1087 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1088 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1089 }) => ensure!(1090 new_limit <= MAX_SPONSOR_TIMEOUT,1091 <Error<T>>::CollectionLimitBoundsExceeded,1092 ),1093 sponsored_data_size => ensure!(1094 new_limit <= CUSTOM_DATA_LIMIT,1095 <Error<T>>::CollectionLimitBoundsExceeded,1096 ),1097 token_limit => ensure!(1098 old_limit >= new_limit && new_limit > 0,1099 <Error<T>>::CollectionTokenLimitExceeded1100 ),1101 owner_can_transfer => ensure!(1102 old_limit || !new_limit,1103 <Error<T>>::OwnerPermissionsCantBeReverted,1104 ),1105 owner_can_destroy => ensure!(1106 old_limit || !new_limit,1107 <Error<T>>::OwnerPermissionsCantBeReverted,1108 ),1109 sponsored_data_rate_limit => {},1110 transfers_enabled => {},1111 );1112 Ok(new_limit)1113 }1114}11151116#[macro_export]1117macro_rules! unsupported {1118 () => {1119 Err(<Error<T>>::UnsupportedOperation.into())1120 };1121}112211231124pub trait CommonWeightInfo<CrossAccountId> {1125 fn create_item() -> Weight;1126 fn create_multiple_items(amount: u32) -> Weight;1127 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1128 fn burn_item() -> Weight;1129 fn set_collection_properties(amount: u32) -> Weight;1130 fn delete_collection_properties(amount: u32) -> Weight;1131 fn set_token_properties(amount: u32) -> Weight;1132 fn delete_token_properties(amount: u32) -> Weight;1133 fn set_property_permissions(amount: u32) -> Weight;1134 fn transfer() -> Weight;1135 fn approve() -> Weight;1136 fn transfer_from() -> Weight;1137 fn burn_from() -> Weight;1138}11391140pub trait CommonCollectionOperations<T: Config> {1141 fn create_item(1142 &self,1143 sender: T::CrossAccountId,1144 to: T::CrossAccountId,1145 data: CreateItemData,1146 nesting_budget: &dyn Budget,1147 ) -> DispatchResultWithPostInfo;1148 fn create_multiple_items(1149 &self,1150 sender: T::CrossAccountId,1151 to: T::CrossAccountId,1152 data: Vec<CreateItemData>,1153 nesting_budget: &dyn Budget,1154 ) -> DispatchResultWithPostInfo;1155 fn create_multiple_items_ex(1156 &self,1157 sender: T::CrossAccountId,1158 data: CreateItemExData<T::CrossAccountId>,1159 nesting_budget: &dyn Budget,1160 ) -> DispatchResultWithPostInfo;1161 fn burn_item(1162 &self,1163 sender: T::CrossAccountId,1164 token: TokenId,1165 amount: u128,1166 ) -> DispatchResultWithPostInfo;1167 fn set_collection_properties(1168 &self,1169 sender: T::CrossAccountId,1170 properties: Vec<Property>,1171 ) -> DispatchResultWithPostInfo;1172 fn delete_collection_properties(1173 &self,1174 sender: &T::CrossAccountId,1175 property_keys: Vec<PropertyKey>,1176 ) -> DispatchResultWithPostInfo;1177 fn set_token_properties(1178 &self,1179 sender: T::CrossAccountId,1180 token_id: TokenId,1181 property: Vec<Property>,1182 ) -> DispatchResultWithPostInfo;1183 fn delete_token_properties(1184 &self,1185 sender: T::CrossAccountId,1186 token_id: TokenId,1187 property_keys: Vec<PropertyKey>,1188 ) -> DispatchResultWithPostInfo;1189 fn set_property_permissions(1190 &self,1191 sender: &T::CrossAccountId,1192 property_permissions: Vec<PropertyKeyPermission>,1193 ) -> DispatchResultWithPostInfo;1194 fn transfer(1195 &self,1196 sender: T::CrossAccountId,1197 to: T::CrossAccountId,1198 token: TokenId,1199 amount: u128,1200 nesting_budget: &dyn Budget,1201 ) -> DispatchResultWithPostInfo;1202 fn approve(1203 &self,1204 sender: T::CrossAccountId,1205 spender: T::CrossAccountId,1206 token: TokenId,1207 amount: u128,1208 ) -> DispatchResultWithPostInfo;1209 fn transfer_from(1210 &self,1211 sender: T::CrossAccountId,1212 from: T::CrossAccountId,1213 to: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResultWithPostInfo;1218 fn burn_from(1219 &self,1220 sender: T::CrossAccountId,1221 from: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 nesting_budget: &dyn Budget,1225 ) -> DispatchResultWithPostInfo;12261227 fn check_nesting(1228 &self,1229 sender: T::CrossAccountId,1230 from: (CollectionId, TokenId),1231 under: TokenId,1232 budget: &dyn Budget,1233 ) -> DispatchResult;12341235 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1236 fn collection_tokens(&self) -> Vec<TokenId>;1237 fn token_exists(&self, token: TokenId) -> bool;1238 fn last_token_id(&self) -> TokenId;12391240 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1241 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1242 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1243 1244 fn total_supply(&self) -> u32;1245 1246 fn account_balance(&self, account: T::CrossAccountId) -> u32;1247 1248 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1249 fn allowance(1250 &self,1251 sender: T::CrossAccountId,1252 spender: T::CrossAccountId,1253 token: TokenId,1254 ) -> u128;1255}125612571258pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1259 let post_info = PostDispatchInfo {1260 actual_weight: Some(weight),1261 pays_fee: Pays::Yes,1262 };1263 match res {1264 Ok(()) => Ok(post_info),1265 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1266 }1267}12681269impl<T: Config> From<PropertiesError> for Error<T> {1270 fn from(error: PropertiesError) -> Self {1271 match error {1272 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1273 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1274 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1275 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1276 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1277 }1278 }1279}