1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission, TokenData, TrySet,40 41 RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, RmrkBaseInfo, RmrkPartType, RmrkTheme,42 RmrkNftChild,43};4445pub use pallet::*;46use sp_core::H160;47use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};48#[cfg(feature = "runtime-benchmarks")]49pub mod benchmarking;50pub mod dispatch;51pub mod erc;52pub mod eth;5354#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]55pub struct CollectionHandle<T: Config> {56 pub id: CollectionId,57 collection: Collection<T::AccountId>,58 pub recorder: SubstrateRecorder<T>,59}60impl<T: Config> WithRecorder<T> for CollectionHandle<T> {61 fn recorder(&self) -> &SubstrateRecorder<T> {62 &self.recorder63 }64 fn into_recorder(self) -> SubstrateRecorder<T> {65 self.recorder66 }67}68impl<T: Config> CollectionHandle<T> {69 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {70 <CollectionById<T>>::get(id).map(|collection| Self {71 id,72 collection,73 recorder: SubstrateRecorder::new(gas_limit),74 })75 }76 pub fn new(id: CollectionId) -> Option<Self> {77 Self::new_with_gas_limit(id, u64::MAX)78 }79 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {80 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)81 }82 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {83 self.recorder84 .consume_gas(T::GasWeightMapping::weight_to_gas(85 <T as frame_system::Config>::DbWeight::get()86 .read87 .saturating_mul(reads),88 ))89 }90 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {91 self.recorder92 .consume_gas(T::GasWeightMapping::weight_to_gas(93 <T as frame_system::Config>::DbWeight::get()94 .write95 .saturating_mul(writes),96 ))97 }98 pub fn save(self) -> DispatchResult {99 <CollectionById<T>>::insert(self.id, self.collection);100 Ok(())101 }102}103impl<T: Config> Deref for CollectionHandle<T> {104 type Target = Collection<T::AccountId>;105106 fn deref(&self) -> &Self::Target {107 &self.collection108 }109}110111impl<T: Config> DerefMut for CollectionHandle<T> {112 fn deref_mut(&mut self) -> &mut Self::Target {113 &mut self.collection114 }115}116117impl<T: Config> CollectionHandle<T> {118 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {119 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);120 Ok(())121 }122 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {123 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))124 }125 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {126 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);127 Ok(())128 }129 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {130 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)131 }132 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {136 ensure!(137 <Allowlist<T>>::get((self.id, user)),138 <Error<T>>::AddressNotInAllowlist139 );140 Ok(())141 }142143 pub fn check_can_update_meta(144 &self,145 subject: &T::CrossAccountId,146 item_owner: &T::CrossAccountId,147 ) -> DispatchResult {148 match self.meta_update_permission {149 MetaUpdatePermission::ItemOwner => {150 ensure!(subject == item_owner, <Error<T>>::NoPermission);151 Ok(())152 }153 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),154 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),155 }156 }157}158159#[frame_support::pallet]160pub mod pallet {161 use super::*;162 use pallet_evm::account;163 use dispatch::CollectionDispatch;164 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};165 use frame_system::pallet_prelude::*;166 use frame_support::traits::Currency;167 use up_data_structs::{TokenId, mapping::TokenAddressMapping};168 use scale_info::TypeInfo;169170 #[pallet::config]171 pub trait Config:172 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config173 {174 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;175176 type Currency: Currency<Self::AccountId>;177178 #[pallet::constant]179 type CollectionCreationPrice: Get<180 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,181 >;182 type CollectionDispatch: CollectionDispatch<Self>;183184 type TreasuryAccountId: Get<Self::AccountId>;185186 type EvmTokenAddressMapping: TokenAddressMapping<H160>;187 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;188 }189190 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);191192 #[pallet::pallet]193 #[pallet::storage_version(STORAGE_VERSION)]194 #[pallet::generate_store(pub(super) trait Store)]195 pub struct Pallet<T>(_);196197 #[pallet::extra_constants]198 impl<T: Config> Pallet<T> {199 pub fn collection_admins_limit() -> u32 {200 COLLECTION_ADMINS_LIMIT201 }202 }203204 #[pallet::event]205 #[pallet::generate_deposit(pub fn deposit_event)]206 pub enum Event<T: Config> {207 208 209 210 211 212 213 214 215 216 CollectionCreated(CollectionId, u8, T::AccountId),217218 219 220 221 222 223 CollectionDestroyed(CollectionId),224225 226 227 228 229 230 231 232 233 234 235 236 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),237238 239 240 241 242 243 244 245 246 247 248 249 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),250251 252 253 254 255 256 257 258 259 260 261 262 Transfer(263 CollectionId,264 TokenId,265 T::CrossAccountId,266 T::CrossAccountId,267 u128,268 ),269270 271 272 273 274 275 276 277 278 279 Approved(280 CollectionId,281 TokenId,282 T::CrossAccountId,283 T::CrossAccountId,284 u128,285 ),286287 CollectionPropertySet(CollectionId, PropertyKey),288289 CollectionPropertyDeleted(CollectionId, PropertyKey),290291 TokenPropertySet(CollectionId, TokenId, PropertyKey),292293 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),294295 PropertyPermissionSet(CollectionId, PropertyKey),296 }297298 #[pallet::error]299 pub enum Error<T> {300 301 CollectionNotFound,302 303 MustBeTokenOwner,304 305 NoPermission,306 307 PublicMintingNotAllowed,308 309 AddressNotInAllowlist,310311 312 CollectionNameLimitExceeded,313 314 CollectionDescriptionLimitExceeded,315 316 CollectionTokenPrefixLimitExceeded,317 318 TotalCollectionsLimitExceeded,319 320 TokenVariableDataLimitExceeded,321 322 CollectionAdminCountExceeded,323 324 CollectionLimitBoundsExceeded,325 326 OwnerPermissionsCantBeReverted,327 328 TransferNotAllowed,329 330 AccountTokenLimitExceeded,331 332 CollectionTokenLimitExceeded,333 334 MetadataFlagFrozen,335336 337 TokenNotFound,338 339 TokenValueTooLow,340 341 ApprovedValueTooLow,342 343 CantApproveMoreThanOwned,344345 346 AddressIsZero,347 348 UnsupportedOperation,349350 351 NotSufficientFounds,352353 354 NestingIsDisabled,355 356 OnlyOwnerAllowedToNest,357 358 SourceCollectionIsNotAllowedToNest,359360 361 CollectionFieldSizeExceeded,362363 364 NoSpaceForProperty,365366 367 PropertyLimitReached,368369 370 UnableToReadUnboundedKeys,371372 373 InvalidCharacterInPropertyKey,374375 376 EmptyPropertyKey,377 }378379 #[pallet::storage]380 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;381 #[pallet::storage]382 pub type DestroyedCollectionCount<T> =383 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;384385 386 #[pallet::storage]387 pub type CollectionById<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = Collection<<T as frame_system::Config>::AccountId>,391 QueryKind = OptionQuery,392 >;393394 395 #[pallet::storage]396 #[pallet::getter(fn collection_properties)]397 pub type CollectionProperties<T> = StorageMap<398 Hasher = Blake2_128Concat,399 Key = CollectionId,400 Value = Properties,401 QueryKind = ValueQuery,402 OnEmpty = up_data_structs::CollectionProperties,403 >;404405 #[pallet::storage]406 #[pallet::getter(fn property_permissions)]407 pub type CollectionPropertyPermissions<T> = StorageMap<408 Hasher = Blake2_128Concat,409 Key = CollectionId,410 Value = PropertiesPermissionMap,411 QueryKind = ValueQuery,412 >;413414 415 #[pallet::storage]416 pub type CollectionData<T> = StorageNMap<417 Key = (418 Key<Twox64Concat, CollectionId>,419 Key<Twox64Concat, CollectionField>,420 ),421 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,422 QueryKind = ValueQuery,423 >;424425 #[pallet::storage]426 pub type AdminAmount<T> = StorageMap<427 Hasher = Blake2_128Concat,428 Key = CollectionId,429 Value = u32,430 QueryKind = ValueQuery,431 >;432433 434 #[pallet::storage]435 pub type IsAdmin<T: Config> = StorageNMap<436 Key = (437 Key<Blake2_128Concat, CollectionId>,438 Key<Blake2_128Concat, T::CrossAccountId>,439 ),440 Value = bool,441 QueryKind = ValueQuery,442 >;443444 445 #[pallet::storage]446 pub type Allowlist<T: Config> = StorageNMap<447 Key = (448 Key<Blake2_128Concat, CollectionId>,449 Key<Blake2_128Concat, T::CrossAccountId>,450 ),451 Value = bool,452 QueryKind = ValueQuery,453 >;454455 456 #[pallet::storage]457 pub type DummyStorageValue<T: Config> = StorageValue<458 Value = (459 CollectionStats,460 CollectionId,461 TokenId,462 PhantomType<TokenData<T::CrossAccountId>>,463 PhantomType<RpcCollection<T::AccountId>>,464 465 PhantomType<RmrkCollectionInfo<T::AccountId>>,466 PhantomType<RmrkInstanceInfo<T::AccountId>>,467 PhantomType<RmrkResourceInfo>,468 PhantomType<RmrkPropertyInfo>,469 PhantomType<RmrkBaseInfo<T::AccountId>>,470 PhantomType<RmrkPartType>,471 PhantomType<RmrkTheme>,472 PhantomType<RmrkNftChild>,473 ),474 QueryKind = OptionQuery,475 >;476477 #[pallet::hooks]478 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {479 fn on_runtime_upgrade() -> Weight {480 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {481 use up_data_structs::{CollectionVersion1, CollectionVersion2};482 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {483 Self::set_field_raw(484 id,485 CollectionField::OffchainSchema,486 v.offchain_schema.clone().into_inner(),487 )488 .expect("data has lower bounds than field");489 Self::set_field_raw(490 id,491 CollectionField::ConstOnChainSchema,492 v.const_on_chain_schema.clone().into_inner(),493 )494 .expect("data has lower bounds than field");495496 Some(CollectionVersion2::from(v))497 });498 }499500 0501 }502 }503}504505impl<T: Config> Pallet<T> {506 507 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {508 ensure!(509 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,510 <Error<T>>::AddressIsZero511 );512 Ok(())513 }514 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {515 <IsAdmin<T>>::iter_prefix((collection,))516 .map(|(a, _)| a)517 .collect()518 }519 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {520 <Allowlist<T>>::iter_prefix((collection,))521 .map(|(a, _)| a)522 .collect()523 }524 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {525 <Allowlist<T>>::get((collection, user))526 }527 pub fn collection_stats() -> CollectionStats {528 let created = <CreatedCollectionCount<T>>::get();529 let destroyed = <DestroyedCollectionCount<T>>::get();530 CollectionStats {531 created: created.0,532 destroyed: destroyed.0,533 alive: created.0 - destroyed.0,534 }535 }536537 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {538 let collection = <CollectionById<T>>::get(collection);539 if collection.is_none() {540 return None;541 }542543 let collection = collection.unwrap();544 let limits = collection.limits;545 let effective_limits = CollectionLimits {546 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),547 sponsored_data_size: Some(limits.sponsored_data_size()),548 sponsored_data_rate_limit: Some(549 limits550 .sponsored_data_rate_limit551 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),552 ),553 token_limit: Some(limits.token_limit()),554 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(555 match collection.mode {556 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,557 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,558 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,559 },560 )),561 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),562 owner_can_transfer: Some(limits.owner_can_transfer()),563 owner_can_destroy: Some(limits.owner_can_destroy()),564 transfers_enabled: Some(limits.transfers_enabled()),565 nesting_rule: Some(limits.nesting_rule().clone()),566 };567568 Some(effective_limits)569 }570571 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {572 let Collection {573 name,574 description,575 owner,576 mode,577 access,578 token_prefix,579 mint_mode,580 schema_version,581 sponsorship,582 limits,583 meta_update_permission,584 } = <CollectionById<T>>::get(collection)?;585586 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)587 .iter()588 .map(|(key, permission)| PropertyKeyPermission {589 key: key.clone(),590 permission: permission.clone(),591 })592 .collect();593594 let properties = <CollectionProperties<T>>::get(collection)595 .iter()596 .map(|(key, value)| Property {597 key: key.clone(),598 value: value.clone(),599 })600 .collect();601602 Some(RpcCollection {603 name: name.into_inner(),604 description: description.into_inner(),605 owner,606 mode,607 access,608 token_prefix: token_prefix.into_inner(),609 mint_mode,610 schema_version,611 sponsorship,612 limits,613 meta_update_permission,614 offchain_schema: <CollectionData<T>>::get((615 collection,616 CollectionField::OffchainSchema,617 ))618 .into_inner(),619 const_on_chain_schema: <CollectionData<T>>::get((620 collection,621 CollectionField::ConstOnChainSchema,622 ))623 .into_inner(),624 token_property_permissions,625 properties,626 })627 }628}629630impl<T: Config> Pallet<T> {631 pub fn init_collection(632 owner: T::AccountId,633 data: CreateCollectionData<T::AccountId>,634 ) -> Result<CollectionId, DispatchError> {635 {636 ensure!(637 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,638 Error::<T>::CollectionTokenPrefixLimitExceeded639 );640 }641642 let created_count = <CreatedCollectionCount<T>>::get()643 .0644 .checked_add(1)645 .ok_or(ArithmeticError::Overflow)?;646 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;647 let id = CollectionId(created_count);648649 650 ensure!(651 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,652 <Error<T>>::TotalCollectionsLimitExceeded653 );654655 656657 let collection = Collection {658 owner: owner.clone(),659 name: data.name,660 mode: data.mode.clone(),661 mint_mode: false,662 access: data.access.unwrap_or_default(),663 description: data.description,664 token_prefix: data.token_prefix,665 schema_version: data.schema_version.unwrap_or_default(),666 sponsorship: data667 .pending_sponsor668 .map(SponsorshipState::Unconfirmed)669 .unwrap_or_default(),670 limits: data671 .limits672 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))673 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,674 meta_update_permission: data.meta_update_permission.unwrap_or_default(),675 };676677 let mut collection_properties = up_data_structs::CollectionProperties::get();678 collection_properties679 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))680 .map_err(<Error<T>>::from)?;681682 CollectionProperties::<T>::insert(id, collection_properties);683684 let mut token_props_permissions = PropertiesPermissionMap::new();685 token_props_permissions686 .try_set_from_iter(687 data.token_property_permissions688 .into_iter()689 .map(|property| (property.key, property.permission)),690 )691 .map_err(<Error<T>>::from)?;692693 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);694695 696 {697 let mut imbalance =698 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();699 imbalance.subsume(700 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(701 &T::TreasuryAccountId::get(),702 T::CollectionCreationPrice::get(),703 ),704 );705 <T as Config>::Currency::settle(706 &owner,707 imbalance,708 WithdrawReasons::TRANSFER,709 ExistenceRequirement::KeepAlive,710 )711 .map_err(|_| Error::<T>::NotSufficientFounds)?;712 }713714 <CreatedCollectionCount<T>>::put(created_count);715 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));716 <CollectionById<T>>::insert(id, collection);717 Self::set_field_raw(718 id,719 CollectionField::OffchainSchema,720 data.offchain_schema.into_inner(),721 )722 .expect("data has lower bounds than field");723 Self::set_field_raw(724 id,725 CollectionField::ConstOnChainSchema,726 data.const_on_chain_schema.into_inner(),727 )728 .expect("data has lower bounds than field");729 Ok(id)730 }731732 pub fn destroy_collection(733 collection: CollectionHandle<T>,734 sender: &T::CrossAccountId,735 ) -> DispatchResult {736 ensure!(737 collection.limits.owner_can_destroy(),738 <Error<T>>::NoPermission,739 );740 collection.check_is_owner(sender)?;741742 let destroyed_collections = <DestroyedCollectionCount<T>>::get()743 .0744 .checked_add(1)745 .ok_or(ArithmeticError::Overflow)?;746747 748749 <DestroyedCollectionCount<T>>::put(destroyed_collections);750 <CollectionById<T>>::remove(collection.id);751 <CollectionData<T>>::remove_prefix((collection.id,), None);752 <AdminAmount<T>>::remove(collection.id);753 <IsAdmin<T>>::remove_prefix((collection.id,), None);754 <Allowlist<T>>::remove_prefix((collection.id,), None);755756 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));757 Ok(())758 }759760 pub fn set_collection_property(761 collection: &CollectionHandle<T>,762 sender: &T::CrossAccountId,763 property: Property,764 ) -> DispatchResult {765 collection.check_is_owner_or_admin(sender)?;766767 CollectionProperties::<T>::try_mutate(collection.id, |properties| {768 let property = property.clone();769 properties.try_set(property.key, property.value)770 })771 .map_err(<Error<T>>::from)?;772773 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));774775 Ok(())776 }777778 pub fn set_collection_properties(779 collection: &CollectionHandle<T>,780 sender: &T::CrossAccountId,781 properties: Vec<Property>,782 ) -> DispatchResult {783 for property in properties {784 Self::set_collection_property(collection, sender, property)?;785 }786787 Ok(())788 }789790 pub fn delete_collection_property(791 collection: &CollectionHandle<T>,792 sender: &T::CrossAccountId,793 property_key: PropertyKey,794 ) -> DispatchResult {795 collection.check_is_owner_or_admin(sender)?;796797 CollectionProperties::<T>::try_mutate(collection.id, |properties| {798 properties.remove(&property_key)799 })800 .map_err(<Error<T>>::from)?;801802 Self::deposit_event(Event::CollectionPropertyDeleted(803 collection.id,804 property_key,805 ));806807 Ok(())808 }809810 pub fn delete_collection_properties(811 collection: &CollectionHandle<T>,812 sender: &T::CrossAccountId,813 property_keys: Vec<PropertyKey>,814 ) -> DispatchResult {815 for key in property_keys {816 Self::delete_collection_property(collection, sender, key)?;817 }818819 Ok(())820 }821822 pub fn set_property_permission(823 collection: &CollectionHandle<T>,824 sender: &T::CrossAccountId,825 property_permission: PropertyKeyPermission,826 ) -> DispatchResult {827 collection.check_is_owner_or_admin(sender)?;828829 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);830 let current_permission = all_permissions.get(&property_permission.key);831 if matches![832 current_permission,833 Some(PropertyPermission { mutable: false, .. })834 ] {835 return Err(<Error<T>>::NoPermission.into());836 }837838 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {839 let property_permission = property_permission.clone();840 permissions.try_set(property_permission.key, property_permission.permission)841 })842 .map_err(<Error<T>>::from)?;843844 Self::deposit_event(Event::PropertyPermissionSet(845 collection.id,846 property_permission.key,847 ));848849 Ok(())850 }851852 pub fn set_property_permissions(853 collection: &CollectionHandle<T>,854 sender: &T::CrossAccountId,855 property_permissions: Vec<PropertyKeyPermission>,856 ) -> DispatchResult {857 for prop_pemission in property_permissions {858 Self::set_property_permission(collection, sender, prop_pemission)?;859 }860861 Ok(())862 }863864 pub fn bytes_keys_to_property_keys(865 keys: Vec<Vec<u8>>,866 ) -> Result<Vec<PropertyKey>, DispatchError> {867 keys.into_iter()868 .map(|key| -> Result<PropertyKey, DispatchError> {869 key.try_into()870 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())871 })872 .collect::<Result<Vec<PropertyKey>, DispatchError>>()873 }874875 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {876 let key_str = sp_std::str::from_utf8(key.as_slice())877 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;878879 for ch in key_str.chars() {880 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {881 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());882 }883 }884885 Ok(())886 }887888 pub fn filter_collection_properties(889 collection_id: CollectionId,890 keys: Vec<PropertyKey>,891 ) -> Result<Vec<Property>, DispatchError> {892 let properties = Self::collection_properties(collection_id);893894 let properties = keys895 .into_iter()896 .filter_map(|key| {897 properties.get(&key).map(|value| Property {898 key,899 value: value.clone(),900 })901 })902 .collect();903904 Ok(properties)905 }906907 pub fn filter_property_permissions(908 collection_id: CollectionId,909 keys: Vec<PropertyKey>,910 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {911 let permissions = Self::property_permissions(collection_id);912913 let key_permissions = keys914 .into_iter()915 .filter_map(|key| {916 permissions917 .get(&key)918 .map(|permission| PropertyKeyPermission {919 key,920 permission: permission.clone(),921 })922 })923 .collect();924925 Ok(key_permissions)926 }927928 fn set_field_raw(929 collection_id: CollectionId,930 field: CollectionField,931 value: Vec<u8>,932 ) -> DispatchResult {933 if !value.is_empty() {934 <CollectionData<T>>::insert(935 (collection_id, field),936 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,937 )938 } else {939 <CollectionData<T>>::remove((collection_id, field));940 }941 Ok(())942 }943944 pub fn set_field(945 collection: &CollectionHandle<T>,946 sender: &T::CrossAccountId,947 field: CollectionField,948 value: Vec<u8>,949 ) -> DispatchResult {950 collection.check_is_owner_or_admin(sender)?;951952 953954 Self::set_field_raw(collection.id, field, value)955 }956957 pub fn toggle_allowlist(958 collection: &CollectionHandle<T>,959 sender: &T::CrossAccountId,960 user: &T::CrossAccountId,961 allowed: bool,962 ) -> DispatchResult {963 collection.check_is_owner_or_admin(sender)?;964965 966967 if allowed {968 <Allowlist<T>>::insert((collection.id, user), true);969 } else {970 <Allowlist<T>>::remove((collection.id, user));971 }972973 Ok(())974 }975976 pub fn toggle_admin(977 collection: &CollectionHandle<T>,978 sender: &T::CrossAccountId,979 user: &T::CrossAccountId,980 admin: bool,981 ) -> DispatchResult {982 collection.check_is_owner_or_admin(sender)?;983984 let was_admin = <IsAdmin<T>>::get((collection.id, user));985 if was_admin == admin {986 return Ok(());987 }988 let amount = <AdminAmount<T>>::get(collection.id);989990 if admin {991 let amount = amount992 .checked_add(1)993 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;994 ensure!(995 amount <= Self::collection_admins_limit(),996 <Error<T>>::CollectionAdminCountExceeded,997 );998999 10001001 <AdminAmount<T>>::insert(collection.id, amount);1002 <IsAdmin<T>>::insert((collection.id, user), true);1003 } else {1004 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1005 <IsAdmin<T>>::remove((collection.id, user));1006 }10071008 Ok(())1009 }10101011 pub fn clamp_limits(1012 mode: CollectionMode,1013 old_limit: &CollectionLimits,1014 mut new_limit: CollectionLimits,1015 ) -> Result<CollectionLimits, DispatchError> {1016 macro_rules! limit_default {1017 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1018 $(1019 if let Some($new) = $new.$field {1020 let $old = $old.$field($($arg)?);1021 let _ = $new;1022 let _ = $old;1023 $check1024 } else {1025 $new.$field = $old.$field1026 }1027 )*1028 }};1029 }10301031 limit_default!(old_limit, new_limit,1032 account_token_ownership_limit => ensure!(1033 new_limit <= MAX_TOKEN_OWNERSHIP,1034 <Error<T>>::CollectionLimitBoundsExceeded,1035 ),1036 sponsor_transfer_timeout(match mode {1037 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1038 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1039 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1040 }) => ensure!(1041 new_limit <= MAX_SPONSOR_TIMEOUT,1042 <Error<T>>::CollectionLimitBoundsExceeded,1043 ),1044 sponsored_data_size => ensure!(1045 new_limit <= CUSTOM_DATA_LIMIT,1046 <Error<T>>::CollectionLimitBoundsExceeded,1047 ),1048 token_limit => ensure!(1049 old_limit >= new_limit && new_limit > 0,1050 <Error<T>>::CollectionTokenLimitExceeded1051 ),1052 owner_can_transfer => ensure!(1053 old_limit || !new_limit,1054 <Error<T>>::OwnerPermissionsCantBeReverted,1055 ),1056 owner_can_destroy => ensure!(1057 old_limit || !new_limit,1058 <Error<T>>::OwnerPermissionsCantBeReverted,1059 ),1060 sponsored_data_rate_limit => {},1061 transfers_enabled => {},1062 );1063 Ok(new_limit)1064 }1065}10661067#[macro_export]1068macro_rules! unsupported {1069 () => {1070 Err(<Error<T>>::UnsupportedOperation.into())1071 };1072}107310741075pub trait CommonWeightInfo<CrossAccountId> {1076 fn create_item() -> Weight;1077 fn create_multiple_items(amount: u32) -> Weight;1078 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1079 fn burn_item() -> Weight;1080 fn set_collection_properties(amount: u32) -> Weight;1081 fn delete_collection_properties(amount: u32) -> Weight;1082 fn set_token_properties(amount: u32) -> Weight;1083 fn delete_token_properties(amount: u32) -> Weight;1084 fn set_property_permissions(amount: u32) -> Weight;1085 fn transfer() -> Weight;1086 fn approve() -> Weight;1087 fn transfer_from() -> Weight;1088 fn burn_from() -> Weight;1089 fn set_variable_metadata(bytes: u32) -> Weight;1090}10911092pub trait CommonCollectionOperations<T: Config> {1093 fn create_item(1094 &self,1095 sender: T::CrossAccountId,1096 to: T::CrossAccountId,1097 data: CreateItemData,1098 nesting_budget: &dyn Budget,1099 ) -> DispatchResultWithPostInfo;1100 fn create_multiple_items(1101 &self,1102 sender: T::CrossAccountId,1103 to: T::CrossAccountId,1104 data: Vec<CreateItemData>,1105 nesting_budget: &dyn Budget,1106 ) -> DispatchResultWithPostInfo;1107 fn create_multiple_items_ex(1108 &self,1109 sender: T::CrossAccountId,1110 data: CreateItemExData<T::CrossAccountId>,1111 nesting_budget: &dyn Budget,1112 ) -> DispatchResultWithPostInfo;1113 fn burn_item(1114 &self,1115 sender: T::CrossAccountId,1116 token: TokenId,1117 amount: u128,1118 ) -> DispatchResultWithPostInfo;1119 fn set_collection_properties(1120 &self,1121 sender: T::CrossAccountId,1122 properties: Vec<Property>,1123 ) -> DispatchResultWithPostInfo;1124 fn delete_collection_properties(1125 &self,1126 sender: &T::CrossAccountId,1127 property_keys: Vec<PropertyKey>,1128 ) -> DispatchResultWithPostInfo;1129 fn set_token_properties(1130 &self,1131 sender: T::CrossAccountId,1132 token_id: TokenId,1133 property: Vec<Property>,1134 ) -> DispatchResultWithPostInfo;1135 fn delete_token_properties(1136 &self,1137 sender: T::CrossAccountId,1138 token_id: TokenId,1139 property_keys: Vec<PropertyKey>,1140 ) -> DispatchResultWithPostInfo;1141 fn set_property_permissions(1142 &self,1143 sender: &T::CrossAccountId,1144 property_permissions: Vec<PropertyKeyPermission>,1145 ) -> DispatchResultWithPostInfo;1146 fn transfer(1147 &self,1148 sender: T::CrossAccountId,1149 to: T::CrossAccountId,1150 token: TokenId,1151 amount: u128,1152 nesting_budget: &dyn Budget,1153 ) -> DispatchResultWithPostInfo;1154 fn approve(1155 &self,1156 sender: T::CrossAccountId,1157 spender: T::CrossAccountId,1158 token: TokenId,1159 amount: u128,1160 ) -> DispatchResultWithPostInfo;1161 fn transfer_from(1162 &self,1163 sender: T::CrossAccountId,1164 from: T::CrossAccountId,1165 to: T::CrossAccountId,1166 token: TokenId,1167 amount: u128,1168 nesting_budget: &dyn Budget,1169 ) -> DispatchResultWithPostInfo;1170 fn burn_from(1171 &self,1172 sender: T::CrossAccountId,1173 from: T::CrossAccountId,1174 token: TokenId,1175 amount: u128,1176 nesting_budget: &dyn Budget,1177 ) -> DispatchResultWithPostInfo;11781179 fn set_variable_metadata(1180 &self,1181 sender: T::CrossAccountId,1182 token: TokenId,1183 data: BoundedVec<u8, CustomDataLimit>,1184 ) -> DispatchResultWithPostInfo;11851186 fn check_nesting(1187 &self,1188 sender: T::CrossAccountId,1189 from: (CollectionId, TokenId),1190 under: TokenId,1191 budget: &dyn Budget,1192 ) -> DispatchResult;11931194 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1195 fn collection_tokens(&self) -> Vec<TokenId>;1196 fn token_exists(&self, token: TokenId) -> bool;1197 fn last_token_id(&self) -> TokenId;11981199 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1200 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1201 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1202 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1203 1204 fn total_supply(&self) -> u32;1205 1206 fn account_balance(&self, account: T::CrossAccountId) -> u32;1207 1208 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1209 fn allowance(1210 &self,1211 sender: T::CrossAccountId,1212 spender: T::CrossAccountId,1213 token: TokenId,1214 ) -> u128;1215}121612171218pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1219 let post_info = PostDispatchInfo {1220 actual_weight: Some(weight),1221 pays_fee: Pays::Yes,1222 };1223 match res {1224 Ok(()) => Ok(post_info),1225 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1226 }1227}12281229impl<T: Config> From<PropertiesError> for Error<T> {1230 fn from(error: PropertiesError) -> Self {1231 match error {1232 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1233 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1234 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1235 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1236 }1237 }1238}