1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117 pub fn new(id: CollectionId) -> Option<Self> {118 Self::new_with_gas_limit(id, u64::MAX)119 }120 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {121 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)122 }123 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {124 self.recorder125 .consume_gas(T::GasWeightMapping::weight_to_gas(126 <T as frame_system::Config>::DbWeight::get()127 .read128 .saturating_mul(reads),129 ))130 }131 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {132 self.recorder133 .consume_gas(T::GasWeightMapping::weight_to_gas(134 <T as frame_system::Config>::DbWeight::get()135 .write136 .saturating_mul(writes),137 ))138 }139 pub fn save(self) -> DispatchResult {140 <CollectionById<T>>::insert(self.id, self.collection);141 Ok(())142 }143}144impl<T: Config> Deref for CollectionHandle<T> {145 type Target = Collection<T::AccountId>;146147 fn deref(&self) -> &Self::Target {148 &self.collection149 }150}151152impl<T: Config> DerefMut for CollectionHandle<T> {153 fn deref_mut(&mut self) -> &mut Self::Target {154 &mut self.collection155 }156}157158impl<T: Config> CollectionHandle<T> {159 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {160 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);161 Ok(())162 }163 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {164 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))165 }166 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {167 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);168 Ok(())169 }170 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {171 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)172 }173 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {174 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)175 }176 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {177 ensure!(178 <Allowlist<T>>::get((self.id, user)),179 <Error<T>>::AddressNotInAllowlist180 );181 Ok(())182 }183}184185#[frame_support::pallet]186pub mod pallet {187 use super::*;188 use pallet_evm::account;189 use dispatch::CollectionDispatch;190 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};191 use frame_system::pallet_prelude::*;192 use frame_support::traits::Currency;193 use up_data_structs::{TokenId, mapping::TokenAddressMapping};194 use scale_info::TypeInfo;195 use weights::WeightInfo;196197 #[pallet::config]198 pub trait Config:199 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config200 {201 type WeightInfo: WeightInfo;202 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;203204 type Currency: Currency<Self::AccountId>;205206 #[pallet::constant]207 type CollectionCreationPrice: Get<208 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,209 >;210 type CollectionDispatch: CollectionDispatch<Self>;211212 type TreasuryAccountId: Get<Self::AccountId>;213214 type EvmTokenAddressMapping: TokenAddressMapping<H160>;215 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;216 }217218 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);219220 #[pallet::pallet]221 #[pallet::storage_version(STORAGE_VERSION)]222 #[pallet::generate_store(pub(super) trait Store)]223 pub struct Pallet<T>(_);224225 #[pallet::extra_constants]226 impl<T: Config> Pallet<T> {227 pub fn collection_admins_limit() -> u32 {228 COLLECTION_ADMINS_LIMIT229 }230 }231232 #[pallet::event]233 #[pallet::generate_deposit(pub fn deposit_event)]234 pub enum Event<T: Config> {235 236 237 238 239 240 241 242 243 244 CollectionCreated(CollectionId, u8, T::AccountId),245246 247 248 249 250 251 CollectionDestroyed(CollectionId),252253 254 255 256 257 258 259 260 261 262 263 264 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),265266 267 268 269 270 271 272 273 274 275 276 277 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),278279 280 281 282 283 284 285 286 287 288 289 290 Transfer(291 CollectionId,292 TokenId,293 T::CrossAccountId,294 T::CrossAccountId,295 u128,296 ),297298 299 300 301 302 303 304 305 306 307 Approved(308 CollectionId,309 TokenId,310 T::CrossAccountId,311 T::CrossAccountId,312 u128,313 ),314315 CollectionPropertySet(CollectionId, PropertyKey),316317 CollectionPropertyDeleted(CollectionId, PropertyKey),318319 TokenPropertySet(CollectionId, TokenId, PropertyKey),320321 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),322323 PropertyPermissionSet(CollectionId, PropertyKey),324 }325326 #[pallet::error]327 pub enum Error<T> {328 329 CollectionNotFound,330 331 MustBeTokenOwner,332 333 NoPermission,334 335 PublicMintingNotAllowed,336 337 AddressNotInAllowlist,338339 340 CollectionNameLimitExceeded,341 342 CollectionDescriptionLimitExceeded,343 344 CollectionTokenPrefixLimitExceeded,345 346 TotalCollectionsLimitExceeded,347 348 CollectionAdminCountExceeded,349 350 CollectionLimitBoundsExceeded,351 352 OwnerPermissionsCantBeReverted,353 354 TransferNotAllowed,355 356 AccountTokenLimitExceeded,357 358 CollectionTokenLimitExceeded,359 360 MetadataFlagFrozen,361362 363 TokenNotFound,364 365 TokenValueTooLow,366 367 ApprovedValueTooLow,368 369 CantApproveMoreThanOwned,370371 372 AddressIsZero,373 374 UnsupportedOperation,375376 377 NotSufficientFounds,378379 380 NestingIsDisabled,381 382 OnlyOwnerAllowedToNest,383 384 SourceCollectionIsNotAllowedToNest,385386 387 CollectionFieldSizeExceeded,388389 390 NoSpaceForProperty,391392 393 PropertyLimitReached,394395 396 PropertyKeyIsTooLong,397398 399 InvalidCharacterInPropertyKey,400401 402 EmptyPropertyKey,403 }404405 #[pallet::storage]406 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;407 #[pallet::storage]408 pub type DestroyedCollectionCount<T> =409 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;410411 412 #[pallet::storage]413 pub type CollectionById<T> = StorageMap<414 Hasher = Blake2_128Concat,415 Key = CollectionId,416 Value = Collection<<T as frame_system::Config>::AccountId>,417 QueryKind = OptionQuery,418 >;419420 421 #[pallet::storage]422 #[pallet::getter(fn collection_properties)]423 pub type CollectionProperties<T> = StorageMap<424 Hasher = Blake2_128Concat,425 Key = CollectionId,426 Value = Properties,427 QueryKind = ValueQuery,428 OnEmpty = up_data_structs::CollectionProperties,429 >;430431 #[pallet::storage]432 #[pallet::getter(fn property_permissions)]433 pub type CollectionPropertyPermissions<T> = StorageMap<434 Hasher = Blake2_128Concat,435 Key = CollectionId,436 Value = PropertiesPermissionMap,437 QueryKind = ValueQuery,438 >;439440 #[pallet::storage]441 pub type AdminAmount<T> = StorageMap<442 Hasher = Blake2_128Concat,443 Key = CollectionId,444 Value = u32,445 QueryKind = ValueQuery,446 >;447448 449 #[pallet::storage]450 pub type IsAdmin<T: Config> = StorageNMap<451 Key = (452 Key<Blake2_128Concat, CollectionId>,453 Key<Blake2_128Concat, T::CrossAccountId>,454 ),455 Value = bool,456 QueryKind = ValueQuery,457 >;458459 460 #[pallet::storage]461 pub type Allowlist<T: Config> = StorageNMap<462 Key = (463 Key<Blake2_128Concat, CollectionId>,464 Key<Blake2_128Concat, T::CrossAccountId>,465 ),466 Value = bool,467 QueryKind = ValueQuery,468 >;469470 471 #[pallet::storage]472 pub type DummyStorageValue<T: Config> = StorageValue<473 Value = (474 CollectionStats,475 CollectionId,476 TokenId,477 PhantomType<TokenData<T::CrossAccountId>>,478 PhantomType<RpcCollection<T::AccountId>>,479 480 PhantomType<RmrkCollectionInfo<T::AccountId>>,481 PhantomType<RmrkInstanceInfo<T::AccountId>>,482 PhantomType<RmrkResourceInfo>,483 PhantomType<RmrkPropertyInfo>,484 PhantomType<RmrkBaseInfo<T::AccountId>>,485 PhantomType<RmrkPartType>,486 PhantomType<RmrkTheme>,487 PhantomType<RmrkNftChild>,488 ),489 QueryKind = OptionQuery,490 >;491492 #[pallet::hooks]493 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {494 fn on_runtime_upgrade() -> Weight {495 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {496 use up_data_structs::{CollectionVersion1, CollectionVersion2};497 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {498 let mut props = Vec::new();499 if !v.offchain_schema.is_empty() {500 props.push(Property {501 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),502 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),503 });504 }505 if !v.variable_on_chain_schema.is_empty() {506 props.push(Property {507 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),508 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),509 });510 }511 if !v.const_on_chain_schema.is_empty() {512 props.push(Property {513 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),514 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),515 });516 }517 props.push(Property {518 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),519 value: match v.schema_version {520 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),521 SchemaVersion::Unique => b"Unique".as_slice(),522 }.to_vec().try_into().unwrap(),523 });524 Self::set_scoped_collection_properties(525 id,526 PropertyScope::None,527 props.into_iter(),528 ).expect("existing data larger than properties");529 Some(CollectionVersion2::from(v))530 });531 }532533 0534 }535 }536}537538impl<T: Config> Pallet<T> {539 540 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {541 ensure!(542 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,543 <Error<T>>::AddressIsZero544 );545 Ok(())546 }547 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {548 <IsAdmin<T>>::iter_prefix((collection,))549 .map(|(a, _)| a)550 .collect()551 }552 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {553 <Allowlist<T>>::iter_prefix((collection,))554 .map(|(a, _)| a)555 .collect()556 }557 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {558 <Allowlist<T>>::get((collection, user))559 }560 pub fn collection_stats() -> CollectionStats {561 let created = <CreatedCollectionCount<T>>::get();562 let destroyed = <DestroyedCollectionCount<T>>::get();563 CollectionStats {564 created: created.0,565 destroyed: destroyed.0,566 alive: created.0 - destroyed.0,567 }568 }569570 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {571 let collection = <CollectionById<T>>::get(collection);572 if collection.is_none() {573 return None;574 }575576 let collection = collection.unwrap();577 let limits = collection.limits;578 let effective_limits = CollectionLimits {579 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),580 sponsored_data_size: Some(limits.sponsored_data_size()),581 sponsored_data_rate_limit: Some(582 limits583 .sponsored_data_rate_limit584 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),585 ),586 token_limit: Some(limits.token_limit()),587 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(588 match collection.mode {589 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,590 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,591 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,592 },593 )),594 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),595 owner_can_transfer: Some(limits.owner_can_transfer()),596 owner_can_destroy: Some(limits.owner_can_destroy()),597 transfers_enabled: Some(limits.transfers_enabled()),598 };599600 Some(effective_limits)601 }602603 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {604 let Collection {605 name,606 description,607 owner,608 mode,609 token_prefix,610 sponsorship,611 limits,612 permissions,613 } = <CollectionById<T>>::get(collection)?;614615 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)616 .into_iter()617 .map(|(key, permission)| PropertyKeyPermission {618 key,619 permission,620 })621 .collect();622623 let properties = <CollectionProperties<T>>::get(collection)624 .into_iter()625 .map(|(key, value)| Property {626 key,627 value,628 })629 .collect();630631 Some(RpcCollection {632 name: name.into_inner(),633 description: description.into_inner(),634 owner,635 mode,636 token_prefix: token_prefix.into_inner(),637 sponsorship,638 limits,639 permissions,640 token_property_permissions,641 properties,642 })643 }644}645646macro_rules! limit_default {647 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{648 $(649 if let Some($new) = $new.$field {650 let $old = $old.$field($($arg)?);651 let _ = $new;652 let _ = $old;653 $check654 } else {655 $new.$field = $old.$field656 }657 )*658 }};659}660macro_rules! limit_default_clone {661 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{662 $(663 if let Some($new) = $new.$field.clone() {664 let $old = $old.$field($($arg)?);665 let _ = $new;666 let _ = $old;667 $check668 } else {669 $new.$field = $old.$field.clone()670 }671 )*672 }};673}674675impl<T: Config> Pallet<T> {676 pub fn init_collection(677 owner: T::AccountId,678 data: CreateCollectionData<T::AccountId>,679 ) -> Result<CollectionId, DispatchError> {680 {681 ensure!(682 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,683 Error::<T>::CollectionTokenPrefixLimitExceeded684 );685 }686687 let created_count = <CreatedCollectionCount<T>>::get()688 .0689 .checked_add(1)690 .ok_or(ArithmeticError::Overflow)?;691 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;692 let id = CollectionId(created_count);693694 695 ensure!(696 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,697 <Error<T>>::TotalCollectionsLimitExceeded698 );699700 701702 let collection = Collection {703 owner: owner.clone(),704 name: data.name,705 mode: data.mode.clone(),706 description: data.description,707 token_prefix: data.token_prefix,708 sponsorship: data709 .pending_sponsor710 .map(SponsorshipState::Unconfirmed)711 .unwrap_or_default(),712 limits: data713 .limits714 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))715 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,716 permissions: data717 .permissions718 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))719 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,720 };721722 let mut collection_properties = up_data_structs::CollectionProperties::get();723 collection_properties724 .try_set_from_iter(data.properties.into_iter())725 .map_err(<Error<T>>::from)?;726727 CollectionProperties::<T>::insert(id, collection_properties);728729 let mut token_props_permissions = PropertiesPermissionMap::new();730 token_props_permissions731 .try_set_from_iter(data.token_property_permissions.into_iter())732 .map_err(<Error<T>>::from)?;733734 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);735736 737 {738 let mut imbalance =739 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();740 imbalance.subsume(741 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(742 &T::TreasuryAccountId::get(),743 T::CollectionCreationPrice::get(),744 ),745 );746 <T as Config>::Currency::settle(747 &owner,748 imbalance,749 WithdrawReasons::TRANSFER,750 ExistenceRequirement::KeepAlive,751 )752 .map_err(|_| Error::<T>::NotSufficientFounds)?;753 }754755 <CreatedCollectionCount<T>>::put(created_count);756 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));757 <CollectionById<T>>::insert(id, collection);758 Ok(id)759 }760761 pub fn destroy_collection(762 collection: CollectionHandle<T>,763 sender: &T::CrossAccountId,764 ) -> DispatchResult {765 ensure!(766 collection.limits.owner_can_destroy(),767 <Error<T>>::NoPermission,768 );769 collection.check_is_owner(sender)?;770771 let destroyed_collections = <DestroyedCollectionCount<T>>::get()772 .0773 .checked_add(1)774 .ok_or(ArithmeticError::Overflow)?;775776 777778 <DestroyedCollectionCount<T>>::put(destroyed_collections);779 <CollectionById<T>>::remove(collection.id);780 <AdminAmount<T>>::remove(collection.id);781 <IsAdmin<T>>::remove_prefix((collection.id,), None);782 <Allowlist<T>>::remove_prefix((collection.id,), None);783 <CollectionProperties<T>>::remove(collection.id);784785 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));786 Ok(())787 }788789 pub fn set_collection_property(790 collection: &CollectionHandle<T>,791 sender: &T::CrossAccountId,792 property: Property,793 ) -> DispatchResult {794 collection.check_is_owner_or_admin(sender)?;795796 CollectionProperties::<T>::try_mutate(collection.id, |properties| {797 let property = property.clone();798 properties.try_set(property.key, property.value)799 })800 .map_err(<Error<T>>::from)?;801802 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));803804 Ok(())805 }806807 pub fn set_scoped_collection_property(808 collection_id: CollectionId,809 scope: PropertyScope,810 property: Property,811 ) -> DispatchResult {812 CollectionProperties::<T>::try_mutate(collection_id, |properties| {813 properties.try_scoped_set(scope, property.key, property.value)814 })815 .map_err(<Error<T>>::from)?;816817 Ok(())818 }819820 pub fn set_scoped_collection_properties(821 collection_id: CollectionId,822 scope: PropertyScope,823 properties: impl Iterator<Item = Property>,824 ) -> DispatchResult {825 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {826 stored_properties.try_scoped_set_from_iter(scope, properties)827 })828 .map_err(<Error<T>>::from)?;829830 Ok(())831 }832833 #[transactional]834 pub fn set_collection_properties(835 collection: &CollectionHandle<T>,836 sender: &T::CrossAccountId,837 properties: Vec<Property>,838 ) -> DispatchResult {839 for property in properties {840 Self::set_collection_property(collection, sender, property)?;841 }842843 Ok(())844 }845846 pub fn delete_collection_property(847 collection: &CollectionHandle<T>,848 sender: &T::CrossAccountId,849 property_key: PropertyKey,850 ) -> DispatchResult {851 collection.check_is_owner_or_admin(sender)?;852853 CollectionProperties::<T>::try_mutate(collection.id, |properties| {854 properties.remove(&property_key)855 })856 .map_err(<Error<T>>::from)?;857858 Self::deposit_event(Event::CollectionPropertyDeleted(859 collection.id,860 property_key,861 ));862863 Ok(())864 }865866 #[transactional]867 pub fn delete_collection_properties(868 collection: &CollectionHandle<T>,869 sender: &T::CrossAccountId,870 property_keys: Vec<PropertyKey>,871 ) -> DispatchResult {872 for key in property_keys {873 Self::delete_collection_property(collection, sender, key)?;874 }875876 Ok(())877 }878879 880 pub fn set_property_permission_unchecked(881 collection: CollectionId,882 property_permission: PropertyKeyPermission,883 ) -> DispatchResult {884 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {885 permissions.try_set(property_permission.key, property_permission.permission)886 })887 .map_err(<Error<T>>::from)?;888 Ok(())889 }890891 pub fn set_property_permission(892 collection: &CollectionHandle<T>,893 sender: &T::CrossAccountId,894 property_permission: PropertyKeyPermission,895 ) -> DispatchResult {896 collection.check_is_owner_or_admin(sender)?;897898 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);899 let current_permission = all_permissions.get(&property_permission.key);900 if matches![901 current_permission,902 Some(PropertyPermission { mutable: false, .. })903 ] {904 return Err(<Error<T>>::NoPermission.into());905 }906907 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {908 let property_permission = property_permission.clone();909 permissions.try_set(property_permission.key, property_permission.permission)910 })911 .map_err(<Error<T>>::from)?;912913 Self::deposit_event(Event::PropertyPermissionSet(914 collection.id,915 property_permission.key,916 ));917918 Ok(())919 }920921 #[transactional]922 pub fn set_property_permissions(923 collection: &CollectionHandle<T>,924 sender: &T::CrossAccountId,925 property_permissions: Vec<PropertyKeyPermission>,926 ) -> DispatchResult {927 for prop_pemission in property_permissions {928 Self::set_property_permission(collection, sender, prop_pemission)?;929 }930931 Ok(())932 }933934 pub fn get_collection_property(935 collection_id: CollectionId,936 key: &PropertyKey,937 ) -> Option<PropertyValue> {938 Self::collection_properties(collection_id).get(key).cloned()939 }940941 pub fn bytes_keys_to_property_keys(942 keys: Vec<Vec<u8>>,943 ) -> Result<Vec<PropertyKey>, DispatchError> {944 keys.into_iter()945 .map(|key| -> Result<PropertyKey, DispatchError> {946 key.try_into()947 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())948 })949 .collect::<Result<Vec<PropertyKey>, DispatchError>>()950 }951952 pub fn filter_collection_properties(953 collection_id: CollectionId,954 keys: Option<Vec<PropertyKey>>,955 ) -> Result<Vec<Property>, DispatchError> {956 let properties = Self::collection_properties(collection_id);957958 let properties = keys959 .map(|keys| {960 keys.into_iter()961 .filter_map(|key| {962 properties.get(&key).map(|value| Property {963 key,964 value: value.clone(),965 })966 })967 .collect()968 })969 .unwrap_or_else(|| {970 properties971 .into_iter()972 .map(|(key, value)| Property {973 key,974 value,975 })976 .collect()977 });978979 Ok(properties)980 }981982 pub fn filter_property_permissions(983 collection_id: CollectionId,984 keys: Option<Vec<PropertyKey>>,985 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {986 let permissions = Self::property_permissions(collection_id);987988 let key_permissions = keys989 .map(|keys| {990 keys.into_iter()991 .filter_map(|key| {992 permissions993 .get(&key)994 .map(|permission| PropertyKeyPermission {995 key,996 permission: permission.clone(),997 })998 })999 .collect()1000 })1001 .unwrap_or_else(|| {1002 permissions1003 .into_iter()1004 .map(|(key, permission)| PropertyKeyPermission {1005 key,1006 permission,1007 })1008 .collect()1009 });10101011 Ok(key_permissions)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 limit_default!(old_limit, new_limit,1074 account_token_ownership_limit => ensure!(1075 new_limit <= MAX_TOKEN_OWNERSHIP,1076 <Error<T>>::CollectionLimitBoundsExceeded,1077 ),1078 sponsor_transfer_timeout(match mode {1079 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1080 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1081 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1082 }) => ensure!(1083 new_limit <= MAX_SPONSOR_TIMEOUT,1084 <Error<T>>::CollectionLimitBoundsExceeded,1085 ),1086 sponsored_data_size => ensure!(1087 new_limit <= CUSTOM_DATA_LIMIT,1088 <Error<T>>::CollectionLimitBoundsExceeded,1089 ),1090 token_limit => ensure!(1091 old_limit >= new_limit && new_limit > 0,1092 <Error<T>>::CollectionTokenLimitExceeded1093 ),1094 owner_can_transfer => ensure!(1095 old_limit || !new_limit,1096 <Error<T>>::OwnerPermissionsCantBeReverted,1097 ),1098 owner_can_destroy => ensure!(1099 old_limit || !new_limit,1100 <Error<T>>::OwnerPermissionsCantBeReverted,1101 ),1102 sponsored_data_rate_limit => {},1103 transfers_enabled => {},1104 );1105 Ok(new_limit)1106 }1107 pub fn clamp_permissions(1108 mode: CollectionMode,1109 old_limit: &CollectionPermissions,1110 mut new_limit: CollectionPermissions,1111 ) -> Result<CollectionPermissions, DispatchError> {1112 limit_default_clone!(old_limit, new_limit,1113 );1114 Ok(new_limit)1115 }1116}11171118#[macro_export]1119macro_rules! unsupported {1120 () => {1121 Err(<Error<T>>::UnsupportedOperation.into())1122 };1123}112411251126pub trait CommonWeightInfo<CrossAccountId> {1127 fn create_item() -> Weight;1128 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1129 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1130 fn burn_item() -> Weight;1131 fn set_collection_properties(amount: u32) -> Weight;1132 fn delete_collection_properties(amount: u32) -> Weight;1133 fn set_token_properties(amount: u32) -> Weight;1134 fn delete_token_properties(amount: u32) -> Weight;1135 fn set_property_permissions(amount: u32) -> Weight;1136 fn transfer() -> Weight;1137 fn approve() -> Weight;1138 fn transfer_from() -> Weight;1139 fn burn_from() -> Weight;1140}11411142pub trait CommonCollectionOperations<T: Config> {1143 fn create_item(1144 &self,1145 sender: T::CrossAccountId,1146 to: T::CrossAccountId,1147 data: CreateItemData,1148 nesting_budget: &dyn Budget,1149 ) -> DispatchResultWithPostInfo;1150 fn create_multiple_items(1151 &self,1152 sender: T::CrossAccountId,1153 to: T::CrossAccountId,1154 data: Vec<CreateItemData>,1155 nesting_budget: &dyn Budget,1156 ) -> DispatchResultWithPostInfo;1157 fn create_multiple_items_ex(1158 &self,1159 sender: T::CrossAccountId,1160 data: CreateItemExData<T::CrossAccountId>,1161 nesting_budget: &dyn Budget,1162 ) -> DispatchResultWithPostInfo;1163 fn burn_item(1164 &self,1165 sender: T::CrossAccountId,1166 token: TokenId,1167 amount: u128,1168 ) -> DispatchResultWithPostInfo;1169 fn set_collection_properties(1170 &self,1171 sender: T::CrossAccountId,1172 properties: Vec<Property>,1173 ) -> DispatchResultWithPostInfo;1174 fn delete_collection_properties(1175 &self,1176 sender: &T::CrossAccountId,1177 property_keys: Vec<PropertyKey>,1178 ) -> DispatchResultWithPostInfo;1179 fn set_token_properties(1180 &self,1181 sender: T::CrossAccountId,1182 token_id: TokenId,1183 property: Vec<Property>,1184 ) -> DispatchResultWithPostInfo;1185 fn delete_token_properties(1186 &self,1187 sender: T::CrossAccountId,1188 token_id: TokenId,1189 property_keys: Vec<PropertyKey>,1190 ) -> DispatchResultWithPostInfo;1191 fn set_property_permissions(1192 &self,1193 sender: &T::CrossAccountId,1194 property_permissions: Vec<PropertyKeyPermission>,1195 ) -> DispatchResultWithPostInfo;1196 fn transfer(1197 &self,1198 sender: T::CrossAccountId,1199 to: T::CrossAccountId,1200 token: TokenId,1201 amount: u128,1202 nesting_budget: &dyn Budget,1203 ) -> DispatchResultWithPostInfo;1204 fn approve(1205 &self,1206 sender: T::CrossAccountId,1207 spender: T::CrossAccountId,1208 token: TokenId,1209 amount: u128,1210 ) -> DispatchResultWithPostInfo;1211 fn transfer_from(1212 &self,1213 sender: T::CrossAccountId,1214 from: T::CrossAccountId,1215 to: T::CrossAccountId,1216 token: TokenId,1217 amount: u128,1218 nesting_budget: &dyn Budget,1219 ) -> DispatchResultWithPostInfo;1220 fn burn_from(1221 &self,1222 sender: T::CrossAccountId,1223 from: T::CrossAccountId,1224 token: TokenId,1225 amount: u128,1226 nesting_budget: &dyn Budget,1227 ) -> DispatchResultWithPostInfo;12281229 fn check_nesting(1230 &self,1231 sender: T::CrossAccountId,1232 from: (CollectionId, TokenId),1233 under: TokenId,1234 budget: &dyn Budget,1235 ) -> DispatchResult;12361237 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1238 fn collection_tokens(&self) -> Vec<TokenId>;1239 fn token_exists(&self, token: TokenId) -> bool;1240 fn last_token_id(&self) -> TokenId;12411242 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1243 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1244 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1245 1246 fn total_supply(&self) -> u32;1247 1248 fn account_balance(&self, account: T::CrossAccountId) -> u32;1249 1250 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1251 fn allowance(1252 &self,1253 sender: T::CrossAccountId,1254 spender: T::CrossAccountId,1255 token: TokenId,1256 ) -> u128;1257}125812591260pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1261 let post_info = PostDispatchInfo {1262 actual_weight: Some(weight),1263 pays_fee: Pays::Yes,1264 };1265 match res {1266 Ok(()) => Ok(post_info),1267 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1268 }1269}12701271impl<T: Config> From<PropertiesError> for Error<T> {1272 fn from(error: PropertiesError) -> Self {1273 match error {1274 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1275 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1276 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1277 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1278 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1279 }1280 }1281}