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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,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 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156157 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158 if self.collection.sponsorship.pending_sponsor() != Some(sender) {159 return false;160 };161162 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163 true164 }165}166impl<T: Config> Deref for CollectionHandle<T> {167 type Target = Collection<T::AccountId>;168169 fn deref(&self) -> &Self::Target {170 &self.collection171 }172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175 fn deref_mut(&mut self) -> &mut Self::Target {176 &mut self.collection177 }178}179180impl<T: Config> CollectionHandle<T> {181 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183 Ok(())184 }185 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187 }188 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190 Ok(())191 }192 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194 }195 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197 }198 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199 ensure!(200 <Allowlist<T>>::get((self.id, user)),201 <Error<T>>::AddressNotInAllowlist202 );203 Ok(())204 }205}206207#[frame_support::pallet]208pub mod pallet {209 use super::*;210 use pallet_evm::account;211 use dispatch::CollectionDispatch;212 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213 use frame_system::pallet_prelude::*;214 use frame_support::traits::Currency;215 use up_data_structs::{TokenId, mapping::TokenAddressMapping};216 use scale_info::TypeInfo;217 use weights::WeightInfo;218219 #[pallet::config]220 pub trait Config:221 frame_system::Config222 + pallet_evm_coder_substrate::Config223 + pallet_evm::Config224 + TypeInfo225 + account::Config226 {227 type WeightInfo: WeightInfo;228 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;229230 type Currency: Currency<Self::AccountId>;231232 #[pallet::constant]233 type CollectionCreationPrice: Get<234 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,235 >;236 type CollectionDispatch: CollectionDispatch<Self>;237238 type TreasuryAccountId: Get<Self::AccountId>;239 type ContractAddress: Get<H160>;240241 type EvmTokenAddressMapping: TokenAddressMapping<H160>;242 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;243 }244245 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);246247 #[pallet::pallet]248 #[pallet::storage_version(STORAGE_VERSION)]249 #[pallet::generate_store(pub(super) trait Store)]250 pub struct Pallet<T>(_);251252 #[pallet::extra_constants]253 impl<T: Config> Pallet<T> {254 pub fn collection_admins_limit() -> u32 {255 COLLECTION_ADMINS_LIMIT256 }257 }258259 #[pallet::event]260 #[pallet::generate_deposit(pub fn deposit_event)]261 pub enum Event<T: Config> {262 263 264 265 266 267 268 269 270 271 CollectionCreated(CollectionId, u8, T::AccountId),272273 274 275 276 277 278 CollectionDestroyed(CollectionId),279280 281 282 283 284 285 286 287 288 289 290 291 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),292293 294 295 296 297 298 299 300 301 302 303 304 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),305306 307 308 309 310 311 312 313 314 315 316 317 Transfer(318 CollectionId,319 TokenId,320 T::CrossAccountId,321 T::CrossAccountId,322 u128,323 ),324325 326 327 328 329 330 331 332 333 334 Approved(335 CollectionId,336 TokenId,337 T::CrossAccountId,338 T::CrossAccountId,339 u128,340 ),341342 CollectionPropertySet(CollectionId, PropertyKey),343344 CollectionPropertyDeleted(CollectionId, PropertyKey),345346 TokenPropertySet(CollectionId, TokenId, PropertyKey),347348 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),349350 PropertyPermissionSet(CollectionId, PropertyKey),351 }352353 #[pallet::error]354 pub enum Error<T> {355 356 CollectionNotFound,357 358 MustBeTokenOwner,359 360 NoPermission,361 362 CantDestroyNotEmptyCollection,363 364 PublicMintingNotAllowed,365 366 AddressNotInAllowlist,367368 369 CollectionNameLimitExceeded,370 371 CollectionDescriptionLimitExceeded,372 373 CollectionTokenPrefixLimitExceeded,374 375 TotalCollectionsLimitExceeded,376 377 CollectionAdminCountExceeded,378 379 CollectionLimitBoundsExceeded,380 381 OwnerPermissionsCantBeReverted,382 383 TransferNotAllowed,384 385 AccountTokenLimitExceeded,386 387 CollectionTokenLimitExceeded,388 389 MetadataFlagFrozen,390391 392 TokenNotFound,393 394 TokenValueTooLow,395 396 ApprovedValueTooLow,397 398 CantApproveMoreThanOwned,399400 401 AddressIsZero,402 403 UnsupportedOperation,404405 406 NotSufficientFounds,407408 409 NestingIsDisabled,410 411 OnlyOwnerAllowedToNest,412 413 SourceCollectionIsNotAllowedToNest,414415 416 CollectionFieldSizeExceeded,417418 419 NoSpaceForProperty,420421 422 PropertyLimitReached,423424 425 PropertyKeyIsTooLong,426427 428 InvalidCharacterInPropertyKey,429430 431 EmptyPropertyKey,432 }433434 #[pallet::storage]435 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;436 #[pallet::storage]437 pub type DestroyedCollectionCount<T> =438 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;439440 441 #[pallet::storage]442 pub type CollectionById<T> = StorageMap<443 Hasher = Blake2_128Concat,444 Key = CollectionId,445 Value = Collection<<T as frame_system::Config>::AccountId>,446 QueryKind = OptionQuery,447 >;448449 450 #[pallet::storage]451 #[pallet::getter(fn collection_properties)]452 pub type CollectionProperties<T> = StorageMap<453 Hasher = Blake2_128Concat,454 Key = CollectionId,455 Value = Properties,456 QueryKind = ValueQuery,457 OnEmpty = up_data_structs::CollectionProperties,458 >;459460 #[pallet::storage]461 #[pallet::getter(fn property_permissions)]462 pub type CollectionPropertyPermissions<T> = StorageMap<463 Hasher = Blake2_128Concat,464 Key = CollectionId,465 Value = PropertiesPermissionMap,466 QueryKind = ValueQuery,467 >;468469 #[pallet::storage]470 pub type AdminAmount<T> = StorageMap<471 Hasher = Blake2_128Concat,472 Key = CollectionId,473 Value = u32,474 QueryKind = ValueQuery,475 >;476477 478 #[pallet::storage]479 pub type IsAdmin<T: Config> = StorageNMap<480 Key = (481 Key<Blake2_128Concat, CollectionId>,482 Key<Blake2_128Concat, T::CrossAccountId>,483 ),484 Value = bool,485 QueryKind = ValueQuery,486 >;487488 489 #[pallet::storage]490 pub type Allowlist<T: Config> = StorageNMap<491 Key = (492 Key<Blake2_128Concat, CollectionId>,493 Key<Blake2_128Concat, T::CrossAccountId>,494 ),495 Value = bool,496 QueryKind = ValueQuery,497 >;498499 500 #[pallet::storage]501 pub type DummyStorageValue<T: Config> = StorageValue<502 Value = (503 CollectionStats,504 CollectionId,505 TokenId,506 TokenChild,507 PhantomType<(508 TokenData<T::CrossAccountId>,509 RpcCollection<T::AccountId>,510 511 RmrkCollectionInfo<T::AccountId>,512 RmrkInstanceInfo<T::AccountId>,513 RmrkResourceInfo,514 RmrkPropertyInfo,515 RmrkBaseInfo<T::AccountId>,516 RmrkPartType,517 RmrkTheme,518 RmrkNftChild,519 )>,520 ),521 QueryKind = OptionQuery,522 >;523524 #[pallet::hooks]525 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {526 fn on_runtime_upgrade() -> Weight {527 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {528 use up_data_structs::{CollectionVersion1, CollectionVersion2};529 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {530 let mut props = Vec::new();531 if !v.offchain_schema.is_empty() {532 props.push(Property {533 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),534 value: v535 .offchain_schema536 .clone()537 .into_inner()538 .try_into()539 .expect("offchain schema too big"),540 });541 }542 if !v.variable_on_chain_schema.is_empty() {543 props.push(Property {544 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),545 value: v546 .variable_on_chain_schema547 .clone()548 .into_inner()549 .try_into()550 .expect("offchain schema too big"),551 });552 }553 if !v.const_on_chain_schema.is_empty() {554 props.push(Property {555 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),556 value: v557 .const_on_chain_schema558 .clone()559 .into_inner()560 .try_into()561 .expect("offchain schema too big"),562 });563 }564 props.push(Property {565 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),566 value: match v.schema_version {567 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),568 SchemaVersion::Unique => b"Unique".as_slice(),569 }570 .to_vec()571 .try_into()572 .unwrap(),573 });574 Self::set_scoped_collection_properties(575 id,576 PropertyScope::None,577 props.into_iter(),578 )579 .expect("existing data larger than properties");580 let mut new = CollectionVersion2::from(v.clone());581 new.permissions.access = Some(v.access);582 new.permissions.mint_mode = Some(v.mint_mode);583 Some(new)584 });585 }586587 0588 }589 }590}591592impl<T: Config> Pallet<T> {593 594 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {595 ensure!(596 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,597 <Error<T>>::AddressIsZero598 );599 Ok(())600 }601 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {602 <IsAdmin<T>>::iter_prefix((collection,))603 .map(|(a, _)| a)604 .collect()605 }606 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {607 <Allowlist<T>>::iter_prefix((collection,))608 .map(|(a, _)| a)609 .collect()610 }611 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {612 <Allowlist<T>>::get((collection, user))613 }614 pub fn collection_stats() -> CollectionStats {615 let created = <CreatedCollectionCount<T>>::get();616 let destroyed = <DestroyedCollectionCount<T>>::get();617 CollectionStats {618 created: created.0,619 destroyed: destroyed.0,620 alive: created.0 - destroyed.0,621 }622 }623624 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {625 let collection = <CollectionById<T>>::get(collection);626 if collection.is_none() {627 return None;628 }629630 let collection = collection.unwrap();631 let limits = collection.limits;632 let effective_limits = CollectionLimits {633 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),634 sponsored_data_size: Some(limits.sponsored_data_size()),635 sponsored_data_rate_limit: Some(636 limits637 .sponsored_data_rate_limit638 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),639 ),640 token_limit: Some(limits.token_limit()),641 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(642 match collection.mode {643 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,644 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,645 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,646 },647 )),648 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),649 owner_can_transfer: Some(limits.owner_can_transfer()),650 owner_can_destroy: Some(limits.owner_can_destroy()),651 transfers_enabled: Some(limits.transfers_enabled()),652 };653654 Some(effective_limits)655 }656657 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {658 let Collection {659 name,660 description,661 owner,662 mode,663 token_prefix,664 sponsorship,665 limits,666 permissions,667 } = <CollectionById<T>>::get(collection)?;668669 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)670 .into_iter()671 .map(|(key, permission)| PropertyKeyPermission { key, permission })672 .collect();673674 let properties = <CollectionProperties<T>>::get(collection)675 .into_iter()676 .map(|(key, value)| Property { key, value })677 .collect();678679 let permissions = CollectionPermissions {680 access: Some(permissions.access()),681 mint_mode: Some(permissions.mint_mode()),682 nesting: Some(permissions.nesting().clone()),683 };684685 Some(RpcCollection {686 name: name.into_inner(),687 description: description.into_inner(),688 owner,689 mode,690 token_prefix: token_prefix.into_inner(),691 sponsorship,692 limits,693 permissions,694 token_property_permissions,695 properties,696 })697 }698}699700macro_rules! limit_default {701 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{702 $(703 if let Some($new) = $new.$field {704 let $old = $old.$field($($arg)?);705 let _ = $new;706 let _ = $old;707 $check708 } else {709 $new.$field = $old.$field710 }711 )*712 }};713}714macro_rules! limit_default_clone {715 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{716 $(717 if let Some($new) = $new.$field.clone() {718 let $old = $old.$field($($arg)?);719 let _ = $new;720 let _ = $old;721 $check722 } else {723 $new.$field = $old.$field.clone()724 }725 )*726 }};727}728729impl<T: Config> Pallet<T> {730 pub fn init_collection(731 owner: T::CrossAccountId,732 data: CreateCollectionData<T::AccountId>,733 ) -> Result<CollectionId, DispatchError> {734 {735 ensure!(736 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,737 Error::<T>::CollectionTokenPrefixLimitExceeded738 );739 }740741 let created_count = <CreatedCollectionCount<T>>::get()742 .0743 .checked_add(1)744 .ok_or(ArithmeticError::Overflow)?;745 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;746 let id = CollectionId(created_count);747748 749 ensure!(750 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,751 <Error<T>>::TotalCollectionsLimitExceeded752 );753754 755756 let collection = Collection {757 owner: owner.as_sub().clone(),758 name: data.name,759 mode: data.mode.clone(),760 description: data.description,761 token_prefix: data.token_prefix,762 sponsorship: data763 .pending_sponsor764 .map(SponsorshipState::Unconfirmed)765 .unwrap_or_default(),766 limits: data767 .limits768 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))769 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,770 permissions: data771 .permissions772 .map(|permissions| {773 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)774 })775 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,776 };777778 let mut collection_properties = up_data_structs::CollectionProperties::get();779 collection_properties780 .try_set_from_iter(data.properties.into_iter())781 .map_err(<Error<T>>::from)?;782783 CollectionProperties::<T>::insert(id, collection_properties);784785 let mut token_props_permissions = PropertiesPermissionMap::new();786 token_props_permissions787 .try_set_from_iter(data.token_property_permissions.into_iter())788 .map_err(<Error<T>>::from)?;789790 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);791792 793 {794 let mut imbalance =795 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();796 imbalance.subsume(797 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(798 &T::TreasuryAccountId::get(),799 T::CollectionCreationPrice::get(),800 ),801 );802 <T as Config>::Currency::settle(803 &owner.as_sub(),804 imbalance,805 WithdrawReasons::TRANSFER,806 ExistenceRequirement::KeepAlive,807 )808 .map_err(|_| Error::<T>::NotSufficientFounds)?;809 }810811 <CreatedCollectionCount<T>>::put(created_count);812 <Pallet<T>>::deposit_event(Event::CollectionCreated(813 id,814 data.mode.id(),815 owner.as_sub().clone(),816 ));817 <PalletEvm<T>>::deposit_log(818 erc::CollectionHelpersEvents::CollectionCreated {819 owner: *owner.as_eth(),820 collection_id: eth::collection_id_to_address(id),821 }822 .to_log(T::ContractAddress::get()),823 );824 <CollectionById<T>>::insert(id, collection);825 Ok(id)826 }827828 pub fn destroy_collection(829 collection: CollectionHandle<T>,830 sender: &T::CrossAccountId,831 ) -> DispatchResult {832 ensure!(833 collection.limits.owner_can_destroy(),834 <Error<T>>::NoPermission,835 );836 collection.check_is_owner(sender)?;837838 let destroyed_collections = <DestroyedCollectionCount<T>>::get()839 .0840 .checked_add(1)841 .ok_or(ArithmeticError::Overflow)?;842843 844845 <DestroyedCollectionCount<T>>::put(destroyed_collections);846 <CollectionById<T>>::remove(collection.id);847 <AdminAmount<T>>::remove(collection.id);848 <IsAdmin<T>>::remove_prefix((collection.id,), None);849 <Allowlist<T>>::remove_prefix((collection.id,), None);850 <CollectionProperties<T>>::remove(collection.id);851852 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));853 Ok(())854 }855856 pub fn set_collection_property(857 collection: &CollectionHandle<T>,858 sender: &T::CrossAccountId,859 property: Property,860 ) -> DispatchResult {861 collection.check_is_owner_or_admin(sender)?;862863 CollectionProperties::<T>::try_mutate(collection.id, |properties| {864 let property = property.clone();865 properties.try_set(property.key, property.value)866 })867 .map_err(<Error<T>>::from)?;868869 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));870871 Ok(())872 }873874 pub fn set_scoped_collection_property(875 collection_id: CollectionId,876 scope: PropertyScope,877 property: Property,878 ) -> DispatchResult {879 CollectionProperties::<T>::try_mutate(collection_id, |properties| {880 properties.try_scoped_set(scope, property.key, property.value)881 })882 .map_err(<Error<T>>::from)?;883884 Ok(())885 }886887 pub fn set_scoped_collection_properties(888 collection_id: CollectionId,889 scope: PropertyScope,890 properties: impl Iterator<Item = Property>,891 ) -> DispatchResult {892 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {893 stored_properties.try_scoped_set_from_iter(scope, properties)894 })895 .map_err(<Error<T>>::from)?;896897 Ok(())898 }899900 #[transactional]901 pub fn set_collection_properties(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 properties: Vec<Property>,905 ) -> DispatchResult {906 for property in properties {907 Self::set_collection_property(collection, sender, property)?;908 }909910 Ok(())911 }912913 pub fn delete_collection_property(914 collection: &CollectionHandle<T>,915 sender: &T::CrossAccountId,916 property_key: PropertyKey,917 ) -> DispatchResult {918 collection.check_is_owner_or_admin(sender)?;919920 CollectionProperties::<T>::try_mutate(collection.id, |properties| {921 properties.remove(&property_key)922 })923 .map_err(<Error<T>>::from)?;924925 Self::deposit_event(Event::CollectionPropertyDeleted(926 collection.id,927 property_key,928 ));929930 Ok(())931 }932933 #[transactional]934 pub fn delete_collection_properties(935 collection: &CollectionHandle<T>,936 sender: &T::CrossAccountId,937 property_keys: Vec<PropertyKey>,938 ) -> DispatchResult {939 for key in property_keys {940 Self::delete_collection_property(collection, sender, key)?;941 }942943 Ok(())944 }945946 947 pub fn set_property_permission_unchecked(948 collection: CollectionId,949 property_permission: PropertyKeyPermission,950 ) -> DispatchResult {951 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {952 permissions.try_set(property_permission.key, property_permission.permission)953 })954 .map_err(<Error<T>>::from)?;955 Ok(())956 }957958 pub fn set_property_permission(959 collection: &CollectionHandle<T>,960 sender: &T::CrossAccountId,961 property_permission: PropertyKeyPermission,962 ) -> DispatchResult {963 collection.check_is_owner_or_admin(sender)?;964965 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);966 let current_permission = all_permissions.get(&property_permission.key);967 if matches![968 current_permission,969 Some(PropertyPermission { mutable: false, .. })970 ] {971 return Err(<Error<T>>::NoPermission.into());972 }973974 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {975 let property_permission = property_permission.clone();976 permissions.try_set(property_permission.key, property_permission.permission)977 })978 .map_err(<Error<T>>::from)?;979980 Self::deposit_event(Event::PropertyPermissionSet(981 collection.id,982 property_permission.key,983 ));984985 Ok(())986 }987988 #[transactional]989 pub fn set_property_permissions(990 collection: &CollectionHandle<T>,991 sender: &T::CrossAccountId,992 property_permissions: Vec<PropertyKeyPermission>,993 ) -> DispatchResult {994 for prop_pemission in property_permissions {995 Self::set_property_permission(collection, sender, prop_pemission)?;996 }997998 Ok(())999 }10001001 pub fn get_collection_property(1002 collection_id: CollectionId,1003 key: &PropertyKey,1004 ) -> Option<PropertyValue> {1005 Self::collection_properties(collection_id).get(key).cloned()1006 }10071008 pub fn bytes_keys_to_property_keys(1009 keys: Vec<Vec<u8>>,1010 ) -> Result<Vec<PropertyKey>, DispatchError> {1011 keys.into_iter()1012 .map(|key| -> Result<PropertyKey, DispatchError> {1013 key.try_into()1014 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1015 })1016 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1017 }10181019 pub fn filter_collection_properties(1020 collection_id: CollectionId,1021 keys: Option<Vec<PropertyKey>>,1022 ) -> Result<Vec<Property>, DispatchError> {1023 let properties = Self::collection_properties(collection_id);10241025 let properties = keys1026 .map(|keys| {1027 keys.into_iter()1028 .filter_map(|key| {1029 properties.get(&key).map(|value| Property {1030 key,1031 value: value.clone(),1032 })1033 })1034 .collect()1035 })1036 .unwrap_or_else(|| {1037 properties1038 .into_iter()1039 .map(|(key, value)| Property { key, value })1040 .collect()1041 });10421043 Ok(properties)1044 }10451046 pub fn filter_property_permissions(1047 collection_id: CollectionId,1048 keys: Option<Vec<PropertyKey>>,1049 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1050 let permissions = Self::property_permissions(collection_id);10511052 let key_permissions = keys1053 .map(|keys| {1054 keys.into_iter()1055 .filter_map(|key| {1056 permissions1057 .get(&key)1058 .map(|permission| PropertyKeyPermission {1059 key,1060 permission: permission.clone(),1061 })1062 })1063 .collect()1064 })1065 .unwrap_or_else(|| {1066 permissions1067 .into_iter()1068 .map(|(key, permission)| PropertyKeyPermission { key, permission })1069 .collect()1070 });10711072 Ok(key_permissions)1073 }10741075 pub fn toggle_allowlist(1076 collection: &CollectionHandle<T>,1077 sender: &T::CrossAccountId,1078 user: &T::CrossAccountId,1079 allowed: bool,1080 ) -> DispatchResult {1081 collection.check_is_owner_or_admin(sender)?;10821083 10841085 if allowed {1086 <Allowlist<T>>::insert((collection.id, user), true);1087 } else {1088 <Allowlist<T>>::remove((collection.id, user));1089 }10901091 Ok(())1092 }10931094 pub fn toggle_admin(1095 collection: &CollectionHandle<T>,1096 sender: &T::CrossAccountId,1097 user: &T::CrossAccountId,1098 admin: bool,1099 ) -> DispatchResult {1100 collection.check_is_owner_or_admin(sender)?;11011102 let was_admin = <IsAdmin<T>>::get((collection.id, user));1103 if was_admin == admin {1104 return Ok(());1105 }1106 let amount = <AdminAmount<T>>::get(collection.id);11071108 if admin {1109 let amount = amount1110 .checked_add(1)1111 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1112 ensure!(1113 amount <= Self::collection_admins_limit(),1114 <Error<T>>::CollectionAdminCountExceeded,1115 );11161117 11181119 <AdminAmount<T>>::insert(collection.id, amount);1120 <IsAdmin<T>>::insert((collection.id, user), true);1121 } else {1122 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1123 <IsAdmin<T>>::remove((collection.id, user));1124 }11251126 Ok(())1127 }11281129 pub fn clamp_limits(1130 mode: CollectionMode,1131 old_limit: &CollectionLimits,1132 mut new_limit: CollectionLimits,1133 ) -> Result<CollectionLimits, DispatchError> {1134 limit_default!(old_limit, new_limit,1135 account_token_ownership_limit => ensure!(1136 new_limit <= MAX_TOKEN_OWNERSHIP,1137 <Error<T>>::CollectionLimitBoundsExceeded,1138 ),1139 sponsored_data_size => ensure!(1140 new_limit <= CUSTOM_DATA_LIMIT,1141 <Error<T>>::CollectionLimitBoundsExceeded,1142 ),11431144 sponsored_data_rate_limit => {},1145 token_limit => ensure!(1146 old_limit >= new_limit && new_limit > 0,1147 <Error<T>>::CollectionTokenLimitExceeded1148 ),11491150 sponsor_transfer_timeout(match mode {1151 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1152 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1153 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1154 }) => ensure!(1155 new_limit <= MAX_SPONSOR_TIMEOUT,1156 <Error<T>>::CollectionLimitBoundsExceeded,1157 ),1158 sponsor_approve_timeout => {},1159 owner_can_transfer => ensure!(1160 old_limit || !new_limit,1161 <Error<T>>::OwnerPermissionsCantBeReverted,1162 ),1163 owner_can_destroy => ensure!(1164 old_limit || !new_limit,1165 <Error<T>>::OwnerPermissionsCantBeReverted,1166 ),1167 transfers_enabled => {},1168 );1169 Ok(new_limit)1170 }11711172 pub fn clamp_permissions(1173 _mode: CollectionMode,1174 old_limit: &CollectionPermissions,1175 mut new_limit: CollectionPermissions,1176 ) -> Result<CollectionPermissions, DispatchError> {1177 limit_default_clone!(old_limit, new_limit,1178 access => {},1179 mint_mode => {},1180 nesting => {},1181 );1182 Ok(new_limit)1183 }1184}11851186#[macro_export]1187macro_rules! unsupported {1188 () => {1189 Err(<Error<T>>::UnsupportedOperation.into())1190 };1191}119211931194pub trait CommonWeightInfo<CrossAccountId> {1195 fn create_item() -> Weight;1196 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1197 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1198 fn burn_item() -> Weight;1199 fn set_collection_properties(amount: u32) -> Weight;1200 fn delete_collection_properties(amount: u32) -> Weight;1201 fn set_token_properties(amount: u32) -> Weight;1202 fn delete_token_properties(amount: u32) -> Weight;1203 fn set_property_permissions(amount: u32) -> Weight;1204 fn transfer() -> Weight;1205 fn approve() -> Weight;1206 fn transfer_from() -> Weight;1207 fn burn_from() -> Weight;12081209 1210 1211 1212 1213 fn burn_recursively_self_raw() -> Weight;1214 1215 1216 1217 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12181219 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1220 Self::burn_recursively_self_raw()1221 .saturating_mul(max_selfs.max(1) as u64)1222 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1223 }1224}12251226pub trait CommonCollectionOperations<T: Config> {1227 fn create_item(1228 &self,1229 sender: T::CrossAccountId,1230 to: T::CrossAccountId,1231 data: CreateItemData,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;1234 fn create_multiple_items(1235 &self,1236 sender: T::CrossAccountId,1237 to: T::CrossAccountId,1238 data: Vec<CreateItemData>,1239 nesting_budget: &dyn Budget,1240 ) -> DispatchResultWithPostInfo;1241 fn create_multiple_items_ex(1242 &self,1243 sender: T::CrossAccountId,1244 data: CreateItemExData<T::CrossAccountId>,1245 nesting_budget: &dyn Budget,1246 ) -> DispatchResultWithPostInfo;1247 fn burn_item(1248 &self,1249 sender: T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 ) -> DispatchResultWithPostInfo;1253 fn burn_item_recursively(1254 &self,1255 sender: T::CrossAccountId,1256 token: TokenId,1257 self_budget: &dyn Budget,1258 breadth_budget: &dyn Budget,1259 ) -> DispatchResultWithPostInfo;1260 fn set_collection_properties(1261 &self,1262 sender: T::CrossAccountId,1263 properties: Vec<Property>,1264 ) -> DispatchResultWithPostInfo;1265 fn delete_collection_properties(1266 &self,1267 sender: &T::CrossAccountId,1268 property_keys: Vec<PropertyKey>,1269 ) -> DispatchResultWithPostInfo;1270 fn set_token_properties(1271 &self,1272 sender: T::CrossAccountId,1273 token_id: TokenId,1274 property: Vec<Property>,1275 ) -> DispatchResultWithPostInfo;1276 fn delete_token_properties(1277 &self,1278 sender: T::CrossAccountId,1279 token_id: TokenId,1280 property_keys: Vec<PropertyKey>,1281 ) -> DispatchResultWithPostInfo;1282 fn set_property_permissions(1283 &self,1284 sender: &T::CrossAccountId,1285 property_permissions: Vec<PropertyKeyPermission>,1286 ) -> DispatchResultWithPostInfo;1287 fn transfer(1288 &self,1289 sender: T::CrossAccountId,1290 to: T::CrossAccountId,1291 token: TokenId,1292 amount: u128,1293 nesting_budget: &dyn Budget,1294 ) -> DispatchResultWithPostInfo;1295 fn approve(1296 &self,1297 sender: T::CrossAccountId,1298 spender: T::CrossAccountId,1299 token: TokenId,1300 amount: u128,1301 ) -> DispatchResultWithPostInfo;1302 fn transfer_from(1303 &self,1304 sender: T::CrossAccountId,1305 from: T::CrossAccountId,1306 to: T::CrossAccountId,1307 token: TokenId,1308 amount: u128,1309 nesting_budget: &dyn Budget,1310 ) -> DispatchResultWithPostInfo;1311 fn burn_from(1312 &self,1313 sender: T::CrossAccountId,1314 from: T::CrossAccountId,1315 token: TokenId,1316 amount: u128,1317 nesting_budget: &dyn Budget,1318 ) -> DispatchResultWithPostInfo;13191320 fn check_nesting(1321 &self,1322 sender: T::CrossAccountId,1323 from: (CollectionId, TokenId),1324 under: TokenId,1325 budget: &dyn Budget,1326 ) -> DispatchResult;13271328 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13291330 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13311332 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1333 fn collection_tokens(&self) -> Vec<TokenId>;1334 fn token_exists(&self, token: TokenId) -> bool;1335 fn last_token_id(&self) -> TokenId;13361337 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1338 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1339 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1340 1341 fn total_supply(&self) -> u32;1342 1343 fn account_balance(&self, account: T::CrossAccountId) -> u32;1344 1345 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1346 fn allowance(1347 &self,1348 sender: T::CrossAccountId,1349 spender: T::CrossAccountId,1350 token: TokenId,1351 ) -> u128;1352}135313541355pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1356 let post_info = PostDispatchInfo {1357 actual_weight: Some(weight),1358 pays_fee: Pays::Yes,1359 };1360 match res {1361 Ok(()) => Ok(post_info),1362 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1363 }1364}13651366impl<T: Config> From<PropertiesError> for Error<T> {1367 fn from(error: PropertiesError) -> Self {1368 match error {1369 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1370 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1371 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1372 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1373 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1374 }1375 }1376}