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 let mut new = CollectionVersion2::from(v.clone());530 new.permissions.access = Some(v.access);531 new.permissions.mint_mode = Some(v.mint_mode);532 Some(new)533 });534 }535536 0537 }538 }539}540541impl<T: Config> Pallet<T> {542 543 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {544 ensure!(545 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,546 <Error<T>>::AddressIsZero547 );548 Ok(())549 }550 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {551 <IsAdmin<T>>::iter_prefix((collection,))552 .map(|(a, _)| a)553 .collect()554 }555 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {556 <Allowlist<T>>::iter_prefix((collection,))557 .map(|(a, _)| a)558 .collect()559 }560 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {561 <Allowlist<T>>::get((collection, user))562 }563 pub fn collection_stats() -> CollectionStats {564 let created = <CreatedCollectionCount<T>>::get();565 let destroyed = <DestroyedCollectionCount<T>>::get();566 CollectionStats {567 created: created.0,568 destroyed: destroyed.0,569 alive: created.0 - destroyed.0,570 }571 }572573 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {574 let collection = <CollectionById<T>>::get(collection);575 if collection.is_none() {576 return None;577 }578579 let collection = collection.unwrap();580 let limits = collection.limits;581 let effective_limits = CollectionLimits {582 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),583 sponsored_data_size: Some(limits.sponsored_data_size()),584 sponsored_data_rate_limit: Some(585 limits586 .sponsored_data_rate_limit587 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),588 ),589 token_limit: Some(limits.token_limit()),590 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(591 match collection.mode {592 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,593 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,594 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,595 },596 )),597 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),598 owner_can_transfer: Some(limits.owner_can_transfer()),599 owner_can_destroy: Some(limits.owner_can_destroy()),600 transfers_enabled: Some(limits.transfers_enabled()),601 };602603 Some(effective_limits)604 }605606 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {607 let Collection {608 name,609 description,610 owner,611 mode,612 token_prefix,613 sponsorship,614 limits,615 permissions,616 } = <CollectionById<T>>::get(collection)?;617618 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)619 .into_iter()620 .map(|(key, permission)| PropertyKeyPermission {621 key,622 permission,623 })624 .collect();625626 let properties = <CollectionProperties<T>>::get(collection)627 .into_iter()628 .map(|(key, value)| Property {629 key,630 value,631 })632 .collect();633634 Some(RpcCollection {635 name: name.into_inner(),636 description: description.into_inner(),637 owner,638 mode,639 token_prefix: token_prefix.into_inner(),640 sponsorship,641 limits,642 permissions,643 token_property_permissions,644 properties,645 })646 }647}648649macro_rules! limit_default {650 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{651 $(652 if let Some($new) = $new.$field {653 let $old = $old.$field($($arg)?);654 let _ = $new;655 let _ = $old;656 $check657 } else {658 $new.$field = $old.$field659 }660 )*661 }};662}663macro_rules! limit_default_clone {664 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{665 $(666 if let Some($new) = $new.$field.clone() {667 let $old = $old.$field($($arg)?);668 let _ = $new;669 let _ = $old;670 $check671 } else {672 $new.$field = $old.$field.clone()673 }674 )*675 }};676}677678impl<T: Config> Pallet<T> {679 pub fn init_collection(680 owner: T::AccountId,681 data: CreateCollectionData<T::AccountId>,682 ) -> Result<CollectionId, DispatchError> {683 {684 ensure!(685 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,686 Error::<T>::CollectionTokenPrefixLimitExceeded687 );688 }689690 let created_count = <CreatedCollectionCount<T>>::get()691 .0692 .checked_add(1)693 .ok_or(ArithmeticError::Overflow)?;694 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;695 let id = CollectionId(created_count);696697 698 ensure!(699 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,700 <Error<T>>::TotalCollectionsLimitExceeded701 );702703 704705 let collection = Collection {706 owner: owner.clone(),707 name: data.name,708 mode: data.mode.clone(),709 description: data.description,710 token_prefix: data.token_prefix,711 sponsorship: data712 .pending_sponsor713 .map(SponsorshipState::Unconfirmed)714 .unwrap_or_default(),715 limits: data716 .limits717 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))718 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,719 permissions: data720 .permissions721 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))722 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,723 };724725 let mut collection_properties = up_data_structs::CollectionProperties::get();726 collection_properties727 .try_set_from_iter(data.properties.into_iter())728 .map_err(<Error<T>>::from)?;729730 CollectionProperties::<T>::insert(id, collection_properties);731732 let mut token_props_permissions = PropertiesPermissionMap::new();733 token_props_permissions734 .try_set_from_iter(data.token_property_permissions.into_iter())735 .map_err(<Error<T>>::from)?;736737 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);738739 740 {741 let mut imbalance =742 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();743 imbalance.subsume(744 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(745 &T::TreasuryAccountId::get(),746 T::CollectionCreationPrice::get(),747 ),748 );749 <T as Config>::Currency::settle(750 &owner,751 imbalance,752 WithdrawReasons::TRANSFER,753 ExistenceRequirement::KeepAlive,754 )755 .map_err(|_| Error::<T>::NotSufficientFounds)?;756 }757758 <CreatedCollectionCount<T>>::put(created_count);759 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));760 <CollectionById<T>>::insert(id, collection);761 Ok(id)762 }763764 pub fn destroy_collection(765 collection: CollectionHandle<T>,766 sender: &T::CrossAccountId,767 ) -> DispatchResult {768 ensure!(769 collection.limits.owner_can_destroy(),770 <Error<T>>::NoPermission,771 );772 collection.check_is_owner(sender)?;773774 let destroyed_collections = <DestroyedCollectionCount<T>>::get()775 .0776 .checked_add(1)777 .ok_or(ArithmeticError::Overflow)?;778779 780781 <DestroyedCollectionCount<T>>::put(destroyed_collections);782 <CollectionById<T>>::remove(collection.id);783 <AdminAmount<T>>::remove(collection.id);784 <IsAdmin<T>>::remove_prefix((collection.id,), None);785 <Allowlist<T>>::remove_prefix((collection.id,), None);786 <CollectionProperties<T>>::remove(collection.id);787788 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));789 Ok(())790 }791792 pub fn set_collection_property(793 collection: &CollectionHandle<T>,794 sender: &T::CrossAccountId,795 property: Property,796 ) -> DispatchResult {797 collection.check_is_owner_or_admin(sender)?;798799 CollectionProperties::<T>::try_mutate(collection.id, |properties| {800 let property = property.clone();801 properties.try_set(property.key, property.value)802 })803 .map_err(<Error<T>>::from)?;804805 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));806807 Ok(())808 }809810 pub fn set_scoped_collection_property(811 collection_id: CollectionId,812 scope: PropertyScope,813 property: Property,814 ) -> DispatchResult {815 CollectionProperties::<T>::try_mutate(collection_id, |properties| {816 properties.try_scoped_set(scope, property.key, property.value)817 })818 .map_err(<Error<T>>::from)?;819820 Ok(())821 }822823 pub fn set_scoped_collection_properties(824 collection_id: CollectionId,825 scope: PropertyScope,826 properties: impl Iterator<Item = Property>,827 ) -> DispatchResult {828 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {829 stored_properties.try_scoped_set_from_iter(scope, properties)830 })831 .map_err(<Error<T>>::from)?;832833 Ok(())834 }835836 #[transactional]837 pub fn set_collection_properties(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 properties: Vec<Property>,841 ) -> DispatchResult {842 for property in properties {843 Self::set_collection_property(collection, sender, property)?;844 }845846 Ok(())847 }848849 pub fn delete_collection_property(850 collection: &CollectionHandle<T>,851 sender: &T::CrossAccountId,852 property_key: PropertyKey,853 ) -> DispatchResult {854 collection.check_is_owner_or_admin(sender)?;855856 CollectionProperties::<T>::try_mutate(collection.id, |properties| {857 properties.remove(&property_key)858 })859 .map_err(<Error<T>>::from)?;860861 Self::deposit_event(Event::CollectionPropertyDeleted(862 collection.id,863 property_key,864 ));865866 Ok(())867 }868869 #[transactional]870 pub fn delete_collection_properties(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 property_keys: Vec<PropertyKey>,874 ) -> DispatchResult {875 for key in property_keys {876 Self::delete_collection_property(collection, sender, key)?;877 }878879 Ok(())880 }881882 883 pub fn set_property_permission_unchecked(884 collection: CollectionId,885 property_permission: PropertyKeyPermission,886 ) -> DispatchResult {887 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {888 permissions.try_set(property_permission.key, property_permission.permission)889 })890 .map_err(<Error<T>>::from)?;891 Ok(())892 }893894 pub fn set_property_permission(895 collection: &CollectionHandle<T>,896 sender: &T::CrossAccountId,897 property_permission: PropertyKeyPermission,898 ) -> DispatchResult {899 collection.check_is_owner_or_admin(sender)?;900901 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);902 let current_permission = all_permissions.get(&property_permission.key);903 if matches![904 current_permission,905 Some(PropertyPermission { mutable: false, .. })906 ] {907 return Err(<Error<T>>::NoPermission.into());908 }909910 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {911 let property_permission = property_permission.clone();912 permissions.try_set(property_permission.key, property_permission.permission)913 })914 .map_err(<Error<T>>::from)?;915916 Self::deposit_event(Event::PropertyPermissionSet(917 collection.id,918 property_permission.key,919 ));920921 Ok(())922 }923924 #[transactional]925 pub fn set_property_permissions(926 collection: &CollectionHandle<T>,927 sender: &T::CrossAccountId,928 property_permissions: Vec<PropertyKeyPermission>,929 ) -> DispatchResult {930 for prop_pemission in property_permissions {931 Self::set_property_permission(collection, sender, prop_pemission)?;932 }933934 Ok(())935 }936937 pub fn get_collection_property(938 collection_id: CollectionId,939 key: &PropertyKey,940 ) -> Option<PropertyValue> {941 Self::collection_properties(collection_id).get(key).cloned()942 }943944 pub fn bytes_keys_to_property_keys(945 keys: Vec<Vec<u8>>,946 ) -> Result<Vec<PropertyKey>, DispatchError> {947 keys.into_iter()948 .map(|key| -> Result<PropertyKey, DispatchError> {949 key.try_into()950 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())951 })952 .collect::<Result<Vec<PropertyKey>, DispatchError>>()953 }954955 pub fn filter_collection_properties(956 collection_id: CollectionId,957 keys: Option<Vec<PropertyKey>>,958 ) -> Result<Vec<Property>, DispatchError> {959 let properties = Self::collection_properties(collection_id);960961 let properties = keys962 .map(|keys| {963 keys.into_iter()964 .filter_map(|key| {965 properties.get(&key).map(|value| Property {966 key,967 value: value.clone(),968 })969 })970 .collect()971 })972 .unwrap_or_else(|| {973 properties974 .into_iter()975 .map(|(key, value)| Property {976 key,977 value,978 })979 .collect()980 });981982 Ok(properties)983 }984985 pub fn filter_property_permissions(986 collection_id: CollectionId,987 keys: Option<Vec<PropertyKey>>,988 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {989 let permissions = Self::property_permissions(collection_id);990991 let key_permissions = keys992 .map(|keys| {993 keys.into_iter()994 .filter_map(|key| {995 permissions996 .get(&key)997 .map(|permission| PropertyKeyPermission {998 key,999 permission: permission.clone(),1000 })1001 })1002 .collect()1003 })1004 .unwrap_or_else(|| {1005 permissions1006 .into_iter()1007 .map(|(key, permission)| PropertyKeyPermission {1008 key,1009 permission,1010 })1011 .collect()1012 });10131014 Ok(key_permissions)1015 }10161017 pub fn toggle_allowlist(1018 collection: &CollectionHandle<T>,1019 sender: &T::CrossAccountId,1020 user: &T::CrossAccountId,1021 allowed: bool,1022 ) -> DispatchResult {1023 collection.check_is_owner_or_admin(sender)?;10241025 10261027 if allowed {1028 <Allowlist<T>>::insert((collection.id, user), true);1029 } else {1030 <Allowlist<T>>::remove((collection.id, user));1031 }10321033 Ok(())1034 }10351036 pub fn toggle_admin(1037 collection: &CollectionHandle<T>,1038 sender: &T::CrossAccountId,1039 user: &T::CrossAccountId,1040 admin: bool,1041 ) -> DispatchResult {1042 collection.check_is_owner_or_admin(sender)?;10431044 let was_admin = <IsAdmin<T>>::get((collection.id, user));1045 if was_admin == admin {1046 return Ok(());1047 }1048 let amount = <AdminAmount<T>>::get(collection.id);10491050 if admin {1051 let amount = amount1052 .checked_add(1)1053 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1054 ensure!(1055 amount <= Self::collection_admins_limit(),1056 <Error<T>>::CollectionAdminCountExceeded,1057 );10581059 10601061 <AdminAmount<T>>::insert(collection.id, amount);1062 <IsAdmin<T>>::insert((collection.id, user), true);1063 } else {1064 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1065 <IsAdmin<T>>::remove((collection.id, user));1066 }10671068 Ok(())1069 }10701071 pub fn clamp_limits(1072 mode: CollectionMode,1073 old_limit: &CollectionLimits,1074 mut new_limit: CollectionLimits,1075 ) -> Result<CollectionLimits, DispatchError> {1076 limit_default!(old_limit, new_limit,1077 account_token_ownership_limit => ensure!(1078 new_limit <= MAX_TOKEN_OWNERSHIP,1079 <Error<T>>::CollectionLimitBoundsExceeded,1080 ),1081 sponsor_transfer_timeout(match mode {1082 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1083 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1084 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1085 }) => ensure!(1086 new_limit <= MAX_SPONSOR_TIMEOUT,1087 <Error<T>>::CollectionLimitBoundsExceeded,1088 ),1089 sponsored_data_size => ensure!(1090 new_limit <= CUSTOM_DATA_LIMIT,1091 <Error<T>>::CollectionLimitBoundsExceeded,1092 ),1093 token_limit => ensure!(1094 old_limit >= new_limit && new_limit > 0,1095 <Error<T>>::CollectionTokenLimitExceeded1096 ),1097 owner_can_transfer => ensure!(1098 old_limit || !new_limit,1099 <Error<T>>::OwnerPermissionsCantBeReverted,1100 ),1101 owner_can_destroy => ensure!(1102 old_limit || !new_limit,1103 <Error<T>>::OwnerPermissionsCantBeReverted,1104 ),1105 sponsored_data_rate_limit => {},1106 transfers_enabled => {},1107 );1108 Ok(new_limit)1109 }1110 pub fn clamp_permissions(1111 mode: CollectionMode,1112 old_limit: &CollectionPermissions,1113 mut new_limit: CollectionPermissions,1114 ) -> Result<CollectionPermissions, DispatchError> {1115 limit_default_clone!(old_limit, new_limit,1116 );1117 Ok(new_limit)1118 }1119}11201121#[macro_export]1122macro_rules! unsupported {1123 () => {1124 Err(<Error<T>>::UnsupportedOperation.into())1125 };1126}112711281129pub trait CommonWeightInfo<CrossAccountId> {1130 fn create_item() -> Weight;1131 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1132 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1133 fn burn_item() -> Weight;1134 fn set_collection_properties(amount: u32) -> Weight;1135 fn delete_collection_properties(amount: u32) -> Weight;1136 fn set_token_properties(amount: u32) -> Weight;1137 fn delete_token_properties(amount: u32) -> Weight;1138 fn set_property_permissions(amount: u32) -> Weight;1139 fn transfer() -> Weight;1140 fn approve() -> Weight;1141 fn transfer_from() -> Weight;1142 fn burn_from() -> Weight;1143}11441145pub trait CommonCollectionOperations<T: Config> {1146 fn create_item(1147 &self,1148 sender: T::CrossAccountId,1149 to: T::CrossAccountId,1150 data: CreateItemData,1151 nesting_budget: &dyn Budget,1152 ) -> DispatchResultWithPostInfo;1153 fn create_multiple_items(1154 &self,1155 sender: T::CrossAccountId,1156 to: T::CrossAccountId,1157 data: Vec<CreateItemData>,1158 nesting_budget: &dyn Budget,1159 ) -> DispatchResultWithPostInfo;1160 fn create_multiple_items_ex(1161 &self,1162 sender: T::CrossAccountId,1163 data: CreateItemExData<T::CrossAccountId>,1164 nesting_budget: &dyn Budget,1165 ) -> DispatchResultWithPostInfo;1166 fn burn_item(1167 &self,1168 sender: T::CrossAccountId,1169 token: TokenId,1170 amount: u128,1171 ) -> DispatchResultWithPostInfo;1172 fn burn_item_unchecked(1173 &self,1174 owner: &T::CrossAccountId,1175 token: TokenId,1176 amount: u128,1177 ) -> DispatchResult;1178 fn set_collection_properties(1179 &self,1180 sender: T::CrossAccountId,1181 properties: Vec<Property>,1182 ) -> DispatchResultWithPostInfo;1183 fn delete_collection_properties(1184 &self,1185 sender: &T::CrossAccountId,1186 property_keys: Vec<PropertyKey>,1187 ) -> DispatchResultWithPostInfo;1188 fn set_token_properties(1189 &self,1190 sender: T::CrossAccountId,1191 token_id: TokenId,1192 property: Vec<Property>,1193 ) -> DispatchResultWithPostInfo;1194 fn delete_token_properties(1195 &self,1196 sender: T::CrossAccountId,1197 token_id: TokenId,1198 property_keys: Vec<PropertyKey>,1199 ) -> DispatchResultWithPostInfo;1200 fn set_property_permissions(1201 &self,1202 sender: &T::CrossAccountId,1203 property_permissions: Vec<PropertyKeyPermission>,1204 ) -> DispatchResultWithPostInfo;1205 fn transfer(1206 &self,1207 sender: T::CrossAccountId,1208 to: T::CrossAccountId,1209 token: TokenId,1210 amount: u128,1211 nesting_budget: &dyn Budget,1212 ) -> DispatchResultWithPostInfo;1213 fn approve(1214 &self,1215 sender: T::CrossAccountId,1216 spender: T::CrossAccountId,1217 token: TokenId,1218 amount: u128,1219 ) -> DispatchResultWithPostInfo;1220 fn transfer_from(1221 &self,1222 sender: T::CrossAccountId,1223 from: T::CrossAccountId,1224 to: T::CrossAccountId,1225 token: TokenId,1226 amount: u128,1227 nesting_budget: &dyn Budget,1228 ) -> DispatchResultWithPostInfo;1229 fn burn_from(1230 &self,1231 sender: T::CrossAccountId,1232 from: T::CrossAccountId,1233 token: TokenId,1234 amount: u128,1235 nesting_budget: &dyn Budget,1236 ) -> DispatchResultWithPostInfo;12371238 fn check_nesting(1239 &self,1240 sender: T::CrossAccountId,1241 from: (CollectionId, TokenId),1242 under: TokenId,1243 budget: &dyn Budget,1244 ) -> DispatchResult;12451246 fn nest(1247 &self,1248 _under: TokenId,1249 _to_nest: (CollectionId, TokenId)1250 ) {}12511252 fn unnest(1253 &self,1254 _under: TokenId,1255 _to_nest: (CollectionId, TokenId)1256 ) {}12571258 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1259 fn collection_tokens(&self) -> Vec<TokenId>;1260 fn token_exists(&self, token: TokenId) -> bool;1261 fn last_token_id(&self) -> TokenId;12621263 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1264 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1265 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1266 1267 fn total_supply(&self) -> u32;1268 1269 fn account_balance(&self, account: T::CrossAccountId) -> u32;1270 1271 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1272 fn allowance(1273 &self,1274 sender: T::CrossAccountId,1275 spender: T::CrossAccountId,1276 token: TokenId,1277 ) -> u128;1278}127912801281pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1282 let post_info = PostDispatchInfo {1283 actual_weight: Some(weight),1284 pays_fee: Pays::Yes,1285 };1286 match res {1287 Ok(()) => Ok(post_info),1288 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1289 }1290}12911292impl<T: Config> From<PropertiesError> for Error<T> {1293 fn from(error: PropertiesError) -> Self {1294 match error {1295 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1296 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1297 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1298 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1299 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1300 }1301 }1302}