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, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission, TokenData, TrySet,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {79 self.recorder80 .consume_gas(T::GasWeightMapping::weight_to_gas(81 <T as frame_system::Config>::DbWeight::get()82 .read83 .saturating_mul(reads),84 ))85 }86 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {87 self.recorder88 .consume_gas(T::GasWeightMapping::weight_to_gas(89 <T as frame_system::Config>::DbWeight::get()90 .write91 .saturating_mul(writes),92 ))93 }94 pub fn save(self) -> DispatchResult {95 <CollectionById<T>>::insert(self.id, self.collection);96 Ok(())97 }98}99impl<T: Config> Deref for CollectionHandle<T> {100 type Target = Collection<T::AccountId>;101102 fn deref(&self) -> &Self::Target {103 &self.collection104 }105}106107impl<T: Config> DerefMut for CollectionHandle<T> {108 fn deref_mut(&mut self) -> &mut Self::Target {109 &mut self.collection110 }111}112113impl<T: Config> CollectionHandle<T> {114 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {115 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);116 Ok(())117 }118 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {119 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))120 }121 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {126 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)127 }128 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {129 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)130 }131 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {132 ensure!(133 <Allowlist<T>>::get((self.id, user)),134 <Error<T>>::AddressNotInAllowlist135 );136 Ok(())137 }138139 pub fn check_can_update_meta(140 &self,141 subject: &T::CrossAccountId,142 item_owner: &T::CrossAccountId,143 ) -> DispatchResult {144 match self.meta_update_permission {145 MetaUpdatePermission::ItemOwner => {146 ensure!(subject == item_owner, <Error<T>>::NoPermission);147 Ok(())148 }149 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),150 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),151 }152 }153}154155#[frame_support::pallet]156pub mod pallet {157 use super::*;158 use pallet_evm::account;159 use dispatch::CollectionDispatch;160 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};161 use frame_system::pallet_prelude::*;162 use frame_support::traits::Currency;163 use up_data_structs::{TokenId, mapping::TokenAddressMapping};164 use scale_info::TypeInfo;165166 #[pallet::config]167 pub trait Config:168 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config169 {170 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;171172 type Currency: Currency<Self::AccountId>;173174 #[pallet::constant]175 type CollectionCreationPrice: Get<176 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,177 >;178 type CollectionDispatch: CollectionDispatch<Self>;179180 type TreasuryAccountId: Get<Self::AccountId>;181182 type EvmTokenAddressMapping: TokenAddressMapping<H160>;183 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;184 }185186 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);187188 #[pallet::pallet]189 #[pallet::storage_version(STORAGE_VERSION)]190 #[pallet::generate_store(pub(super) trait Store)]191 pub struct Pallet<T>(_);192193 #[pallet::extra_constants]194 impl<T: Config> Pallet<T> {195 pub fn collection_admins_limit() -> u32 {196 COLLECTION_ADMINS_LIMIT197 }198 }199200 #[pallet::event]201 #[pallet::generate_deposit(pub fn deposit_event)]202 pub enum Event<T: Config> {203 204 205 206 207 208 209 210 211 212 CollectionCreated(CollectionId, u8, T::AccountId),213214 215 216 217 218 219 CollectionDestroyed(CollectionId),220221 222 223 224 225 226 227 228 229 230 231 232 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),233234 235 236 237 238 239 240 241 242 243 244 245 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),246247 248 249 250 251 252 253 254 255 256 257 258 Transfer(259 CollectionId,260 TokenId,261 T::CrossAccountId,262 T::CrossAccountId,263 u128,264 ),265266 267 268 269 270 271 272 273 274 275 Approved(276 CollectionId,277 TokenId,278 T::CrossAccountId,279 T::CrossAccountId,280 u128,281 ),282283 CollectionPropertySet(CollectionId, PropertyKey),284285 CollectionPropertyDeleted(CollectionId, PropertyKey),286287 TokenPropertySet(CollectionId, TokenId, PropertyKey),288289 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),290291 PropertyPermissionSet(CollectionId, PropertyKey),292 }293294 #[pallet::error]295 pub enum Error<T> {296 297 CollectionNotFound,298 299 MustBeTokenOwner,300 301 NoPermission,302 303 PublicMintingNotAllowed,304 305 AddressNotInAllowlist,306307 308 CollectionNameLimitExceeded,309 310 CollectionDescriptionLimitExceeded,311 312 CollectionTokenPrefixLimitExceeded,313 314 TotalCollectionsLimitExceeded,315 316 CollectionAdminCountExceeded,317 318 CollectionLimitBoundsExceeded,319 320 OwnerPermissionsCantBeReverted,321 322 TransferNotAllowed,323 324 AccountTokenLimitExceeded,325 326 CollectionTokenLimitExceeded,327 328 MetadataFlagFrozen,329330 331 TokenNotFound,332 333 TokenValueTooLow,334 335 ApprovedValueTooLow,336 337 CantApproveMoreThanOwned,338339 340 AddressIsZero,341 342 UnsupportedOperation,343344 345 NotSufficientFounds,346347 348 NestingIsDisabled,349 350 OnlyOwnerAllowedToNest,351 352 SourceCollectionIsNotAllowedToNest,353354 355 CollectionFieldSizeExceeded,356357 358 NoSpaceForProperty,359360 361 PropertyLimitReached,362363 364 UnableToReadUnboundedKeys,365366 367 InvalidCharacterInPropertyKey,368369 370 EmptyPropertyKey,371 }372373 #[pallet::storage]374 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;375 #[pallet::storage]376 pub type DestroyedCollectionCount<T> =377 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;378379 380 #[pallet::storage]381 pub type CollectionById<T> = StorageMap<382 Hasher = Blake2_128Concat,383 Key = CollectionId,384 Value = Collection<<T as frame_system::Config>::AccountId>,385 QueryKind = OptionQuery,386 >;387388 389 #[pallet::storage]390 #[pallet::getter(fn collection_properties)]391 pub type CollectionProperties<T> = StorageMap<392 Hasher = Blake2_128Concat,393 Key = CollectionId,394 Value = Properties,395 QueryKind = ValueQuery,396 OnEmpty = up_data_structs::CollectionProperties,397 >;398399 #[pallet::storage]400 #[pallet::getter(fn property_permissions)]401 pub type CollectionPropertyPermissions<T> = StorageMap<402 Hasher = Blake2_128Concat,403 Key = CollectionId,404 Value = PropertiesPermissionMap,405 QueryKind = ValueQuery,406 >;407408 409 #[pallet::storage]410 pub type CollectionData<T> = StorageNMap<411 Key = (412 Key<Twox64Concat, CollectionId>,413 Key<Twox64Concat, CollectionField>,414 ),415 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,416 QueryKind = ValueQuery,417 >;418419 #[pallet::storage]420 pub type AdminAmount<T> = StorageMap<421 Hasher = Blake2_128Concat,422 Key = CollectionId,423 Value = u32,424 QueryKind = ValueQuery,425 >;426427 428 #[pallet::storage]429 pub type IsAdmin<T: Config> = StorageNMap<430 Key = (431 Key<Blake2_128Concat, CollectionId>,432 Key<Blake2_128Concat, T::CrossAccountId>,433 ),434 Value = bool,435 QueryKind = ValueQuery,436 >;437438 439 #[pallet::storage]440 pub type Allowlist<T: Config> = StorageNMap<441 Key = (442 Key<Blake2_128Concat, CollectionId>,443 Key<Blake2_128Concat, T::CrossAccountId>,444 ),445 Value = bool,446 QueryKind = ValueQuery,447 >;448449 450 #[pallet::storage]451 pub type DummyStorageValue<T: Config> = StorageValue<452 Value = (453 CollectionStats,454 CollectionId,455 TokenId,456 PhantomType<TokenData<T::CrossAccountId>>,457 PhantomType<RpcCollection<T::AccountId>>,458 ),459 QueryKind = OptionQuery,460 >;461462 #[pallet::hooks]463 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {464 fn on_runtime_upgrade() -> Weight {465 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {466 use up_data_structs::{CollectionVersion1, CollectionVersion2};467 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {468 Self::set_field_raw(469 id,470 CollectionField::OffchainSchema,471 v.offchain_schema.clone().into_inner(),472 )473 .expect("data has lower bounds than field");474 Self::set_field_raw(475 id,476 CollectionField::ConstOnChainSchema,477 v.const_on_chain_schema.clone().into_inner(),478 )479 .expect("data has lower bounds than field");480481 Some(CollectionVersion2::from(v))482 });483 }484485 0486 }487 }488}489490impl<T: Config> Pallet<T> {491 492 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {493 ensure!(494 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,495 <Error<T>>::AddressIsZero496 );497 Ok(())498 }499 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {500 <IsAdmin<T>>::iter_prefix((collection,))501 .map(|(a, _)| a)502 .collect()503 }504 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {505 <Allowlist<T>>::iter_prefix((collection,))506 .map(|(a, _)| a)507 .collect()508 }509 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {510 <Allowlist<T>>::get((collection, user))511 }512 pub fn collection_stats() -> CollectionStats {513 let created = <CreatedCollectionCount<T>>::get();514 let destroyed = <DestroyedCollectionCount<T>>::get();515 CollectionStats {516 created: created.0,517 destroyed: destroyed.0,518 alive: created.0 - destroyed.0,519 }520 }521522 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {523 let collection = <CollectionById<T>>::get(collection);524 if collection.is_none() {525 return None;526 }527528 let collection = collection.unwrap();529 let limits = collection.limits;530 let effective_limits = CollectionLimits {531 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),532 sponsored_data_size: Some(limits.sponsored_data_size()),533 sponsored_data_rate_limit: Some(534 limits535 .sponsored_data_rate_limit536 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),537 ),538 token_limit: Some(limits.token_limit()),539 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(540 match collection.mode {541 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,542 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,543 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,544 },545 )),546 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),547 owner_can_transfer: Some(limits.owner_can_transfer()),548 owner_can_destroy: Some(limits.owner_can_destroy()),549 transfers_enabled: Some(limits.transfers_enabled()),550 nesting_rule: Some(limits.nesting_rule().clone()),551 };552553 Some(effective_limits)554 }555556 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {557 let Collection {558 name,559 description,560 owner,561 mode,562 access,563 token_prefix,564 mint_mode,565 schema_version,566 sponsorship,567 limits,568 meta_update_permission,569 } = <CollectionById<T>>::get(collection)?;570571 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)572 .iter()573 .map(|(key, permission)| PropertyKeyPermission {574 key: key.clone(),575 permission: permission.clone(),576 })577 .collect();578579 let properties = <CollectionProperties<T>>::get(collection)580 .iter()581 .map(|(key, value)| Property {582 key: key.clone(),583 value: value.clone(),584 })585 .collect();586587 Some(RpcCollection {588 name: name.into_inner(),589 description: description.into_inner(),590 owner,591 mode,592 access,593 token_prefix: token_prefix.into_inner(),594 mint_mode,595 schema_version,596 sponsorship,597 limits,598 meta_update_permission,599 offchain_schema: <CollectionData<T>>::get((600 collection,601 CollectionField::OffchainSchema,602 ))603 .into_inner(),604 const_on_chain_schema: <CollectionData<T>>::get((605 collection,606 CollectionField::ConstOnChainSchema,607 ))608 .into_inner(),609 token_property_permissions,610 properties,611 })612 }613}614615impl<T: Config> Pallet<T> {616 pub fn init_collection(617 owner: T::AccountId,618 data: CreateCollectionData<T::AccountId>,619 ) -> Result<CollectionId, DispatchError> {620 {621 ensure!(622 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,623 Error::<T>::CollectionTokenPrefixLimitExceeded624 );625 }626627 let created_count = <CreatedCollectionCount<T>>::get()628 .0629 .checked_add(1)630 .ok_or(ArithmeticError::Overflow)?;631 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;632 let id = CollectionId(created_count);633634 635 ensure!(636 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,637 <Error<T>>::TotalCollectionsLimitExceeded638 );639640 641642 let collection = Collection {643 owner: owner.clone(),644 name: data.name,645 mode: data.mode.clone(),646 mint_mode: false,647 access: data.access.unwrap_or_default(),648 description: data.description,649 token_prefix: data.token_prefix,650 schema_version: data.schema_version.unwrap_or_default(),651 sponsorship: data652 .pending_sponsor653 .map(SponsorshipState::Unconfirmed)654 .unwrap_or_default(),655 limits: data656 .limits657 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))658 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,659 meta_update_permission: data.meta_update_permission.unwrap_or_default(),660 };661662 let mut collection_properties = up_data_structs::CollectionProperties::get();663 collection_properties664 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))665 .map_err(<Error<T>>::from)?;666667 CollectionProperties::<T>::insert(id, collection_properties);668669 let mut token_props_permissions = PropertiesPermissionMap::new();670 token_props_permissions671 .try_set_from_iter(672 data.token_property_permissions673 .into_iter()674 .map(|property| (property.key, property.permission)),675 )676 .map_err(<Error<T>>::from)?;677678 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);679680 681 {682 let mut imbalance =683 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();684 imbalance.subsume(685 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(686 &T::TreasuryAccountId::get(),687 T::CollectionCreationPrice::get(),688 ),689 );690 <T as Config>::Currency::settle(691 &owner,692 imbalance,693 WithdrawReasons::TRANSFER,694 ExistenceRequirement::KeepAlive,695 )696 .map_err(|_| Error::<T>::NotSufficientFounds)?;697 }698699 <CreatedCollectionCount<T>>::put(created_count);700 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));701 <CollectionById<T>>::insert(id, collection);702 Self::set_field_raw(703 id,704 CollectionField::OffchainSchema,705 data.offchain_schema.into_inner(),706 )707 .expect("data has lower bounds than field");708 Self::set_field_raw(709 id,710 CollectionField::ConstOnChainSchema,711 data.const_on_chain_schema.into_inner(),712 )713 .expect("data has lower bounds than field");714 Ok(id)715 }716717 pub fn destroy_collection(718 collection: CollectionHandle<T>,719 sender: &T::CrossAccountId,720 ) -> DispatchResult {721 ensure!(722 collection.limits.owner_can_destroy(),723 <Error<T>>::NoPermission,724 );725 collection.check_is_owner(sender)?;726727 let destroyed_collections = <DestroyedCollectionCount<T>>::get()728 .0729 .checked_add(1)730 .ok_or(ArithmeticError::Overflow)?;731732 733734 <DestroyedCollectionCount<T>>::put(destroyed_collections);735 <CollectionById<T>>::remove(collection.id);736 <CollectionData<T>>::remove_prefix((collection.id,), None);737 <AdminAmount<T>>::remove(collection.id);738 <IsAdmin<T>>::remove_prefix((collection.id,), None);739 <Allowlist<T>>::remove_prefix((collection.id,), None);740741 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));742 Ok(())743 }744745 pub fn set_collection_property(746 collection: &CollectionHandle<T>,747 sender: &T::CrossAccountId,748 property: Property,749 ) -> DispatchResult {750 collection.check_is_owner_or_admin(sender)?;751752 CollectionProperties::<T>::try_mutate(collection.id, |properties| {753 let property = property.clone();754 properties.try_set(property.key, property.value)755 })756 .map_err(<Error<T>>::from)?;757758 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));759760 Ok(())761 }762763 pub fn set_collection_properties(764 collection: &CollectionHandle<T>,765 sender: &T::CrossAccountId,766 properties: Vec<Property>,767 ) -> DispatchResult {768 for property in properties {769 Self::set_collection_property(collection, sender, property)?;770 }771772 Ok(())773 }774775 pub fn delete_collection_property(776 collection: &CollectionHandle<T>,777 sender: &T::CrossAccountId,778 property_key: PropertyKey,779 ) -> DispatchResult {780 collection.check_is_owner_or_admin(sender)?;781782 CollectionProperties::<T>::try_mutate(collection.id, |properties| {783 properties.remove(&property_key)784 })785 .map_err(<Error<T>>::from)?;786787 Self::deposit_event(Event::CollectionPropertyDeleted(788 collection.id,789 property_key,790 ));791792 Ok(())793 }794795 pub fn delete_collection_properties(796 collection: &CollectionHandle<T>,797 sender: &T::CrossAccountId,798 property_keys: Vec<PropertyKey>,799 ) -> DispatchResult {800 for key in property_keys {801 Self::delete_collection_property(collection, sender, key)?;802 }803804 Ok(())805 }806807 pub fn set_property_permission(808 collection: &CollectionHandle<T>,809 sender: &T::CrossAccountId,810 property_permission: PropertyKeyPermission,811 ) -> DispatchResult {812 collection.check_is_owner_or_admin(sender)?;813814 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);815 let current_permission = all_permissions.get(&property_permission.key);816 if matches![817 current_permission,818 Some(PropertyPermission { mutable: false, .. })819 ] {820 return Err(<Error<T>>::NoPermission.into());821 }822823 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {824 let property_permission = property_permission.clone();825 permissions.try_set(property_permission.key, property_permission.permission)826 })827 .map_err(<Error<T>>::from)?;828829 Self::deposit_event(Event::PropertyPermissionSet(830 collection.id,831 property_permission.key,832 ));833834 Ok(())835 }836837 pub fn set_property_permissions(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_permissions: Vec<PropertyKeyPermission>,841 ) -> DispatchResult {842 for prop_pemission in property_permissions {843 Self::set_property_permission(collection, sender, prop_pemission)?;844 }845846 Ok(())847 }848849 pub fn bytes_keys_to_property_keys(850 keys: Vec<Vec<u8>>,851 ) -> Result<Vec<PropertyKey>, DispatchError> {852 keys.into_iter()853 .map(|key| -> Result<PropertyKey, DispatchError> {854 key.try_into()855 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())856 })857 .collect::<Result<Vec<PropertyKey>, DispatchError>>()858 }859860 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {861 let key_str = sp_std::str::from_utf8(key.as_slice())862 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;863864 for ch in key_str.chars() {865 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {866 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());867 }868 }869870 Ok(())871 }872873 pub fn filter_collection_properties(874 collection_id: CollectionId,875 keys: Vec<PropertyKey>,876 ) -> Result<Vec<Property>, DispatchError> {877 let properties = Self::collection_properties(collection_id);878879 let properties = keys880 .into_iter()881 .filter_map(|key| {882 properties.get(&key).map(|value| Property {883 key,884 value: value.clone(),885 })886 })887 .collect();888889 Ok(properties)890 }891892 pub fn filter_property_permissions(893 collection_id: CollectionId,894 keys: Vec<PropertyKey>,895 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {896 let permissions = Self::property_permissions(collection_id);897898 let key_permissions = keys899 .into_iter()900 .filter_map(|key| {901 permissions902 .get(&key)903 .map(|permission| PropertyKeyPermission {904 key,905 permission: permission.clone(),906 })907 })908 .collect();909910 Ok(key_permissions)911 }912913 fn set_field_raw(914 collection_id: CollectionId,915 field: CollectionField,916 value: Vec<u8>,917 ) -> DispatchResult {918 if !value.is_empty() {919 <CollectionData<T>>::insert(920 (collection_id, field),921 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,922 )923 } else {924 <CollectionData<T>>::remove((collection_id, field));925 }926 Ok(())927 }928929 pub fn set_field(930 collection: &CollectionHandle<T>,931 sender: &T::CrossAccountId,932 field: CollectionField,933 value: Vec<u8>,934 ) -> DispatchResult {935 collection.check_is_owner_or_admin(sender)?;936937 938939 Self::set_field_raw(collection.id, field, value)940 }941942 pub fn toggle_allowlist(943 collection: &CollectionHandle<T>,944 sender: &T::CrossAccountId,945 user: &T::CrossAccountId,946 allowed: bool,947 ) -> DispatchResult {948 collection.check_is_owner_or_admin(sender)?;949950 951952 if allowed {953 <Allowlist<T>>::insert((collection.id, user), true);954 } else {955 <Allowlist<T>>::remove((collection.id, user));956 }957958 Ok(())959 }960961 pub fn toggle_admin(962 collection: &CollectionHandle<T>,963 sender: &T::CrossAccountId,964 user: &T::CrossAccountId,965 admin: bool,966 ) -> DispatchResult {967 collection.check_is_owner_or_admin(sender)?;968969 let was_admin = <IsAdmin<T>>::get((collection.id, user));970 if was_admin == admin {971 return Ok(());972 }973 let amount = <AdminAmount<T>>::get(collection.id);974975 if admin {976 let amount = amount977 .checked_add(1)978 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;979 ensure!(980 amount <= Self::collection_admins_limit(),981 <Error<T>>::CollectionAdminCountExceeded,982 );983984 985986 <AdminAmount<T>>::insert(collection.id, amount);987 <IsAdmin<T>>::insert((collection.id, user), true);988 } else {989 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));990 <IsAdmin<T>>::remove((collection.id, user));991 }992993 Ok(())994 }995996 pub fn clamp_limits(997 mode: CollectionMode,998 old_limit: &CollectionLimits,999 mut new_limit: CollectionLimits,1000 ) -> Result<CollectionLimits, DispatchError> {1001 macro_rules! limit_default {1002 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1003 $(1004 if let Some($new) = $new.$field {1005 let $old = $old.$field($($arg)?);1006 let _ = $new;1007 let _ = $old;1008 $check1009 } else {1010 $new.$field = $old.$field1011 }1012 )*1013 }};1014 }10151016 limit_default!(old_limit, new_limit,1017 account_token_ownership_limit => ensure!(1018 new_limit <= MAX_TOKEN_OWNERSHIP,1019 <Error<T>>::CollectionLimitBoundsExceeded,1020 ),1021 sponsor_transfer_timeout(match mode {1022 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1023 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1024 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1025 }) => ensure!(1026 new_limit <= MAX_SPONSOR_TIMEOUT,1027 <Error<T>>::CollectionLimitBoundsExceeded,1028 ),1029 sponsored_data_size => ensure!(1030 new_limit <= CUSTOM_DATA_LIMIT,1031 <Error<T>>::CollectionLimitBoundsExceeded,1032 ),1033 token_limit => ensure!(1034 old_limit >= new_limit && new_limit > 0,1035 <Error<T>>::CollectionTokenLimitExceeded1036 ),1037 owner_can_transfer => ensure!(1038 old_limit || !new_limit,1039 <Error<T>>::OwnerPermissionsCantBeReverted,1040 ),1041 owner_can_destroy => ensure!(1042 old_limit || !new_limit,1043 <Error<T>>::OwnerPermissionsCantBeReverted,1044 ),1045 sponsored_data_rate_limit => {},1046 transfers_enabled => {},1047 );1048 Ok(new_limit)1049 }1050}10511052#[macro_export]1053macro_rules! unsupported {1054 () => {1055 Err(<Error<T>>::UnsupportedOperation.into())1056 };1057}105810591060pub trait CommonWeightInfo<CrossAccountId> {1061 fn create_item() -> Weight;1062 fn create_multiple_items(amount: u32) -> Weight;1063 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1064 fn burn_item() -> Weight;1065 fn set_collection_properties(amount: u32) -> Weight;1066 fn delete_collection_properties(amount: u32) -> Weight;1067 fn set_token_properties(amount: u32) -> Weight;1068 fn delete_token_properties(amount: u32) -> Weight;1069 fn set_property_permissions(amount: u32) -> Weight;1070 fn transfer() -> Weight;1071 fn approve() -> Weight;1072 fn transfer_from() -> Weight;1073 fn burn_from() -> Weight;1074}10751076pub trait CommonCollectionOperations<T: Config> {1077 fn create_item(1078 &self,1079 sender: T::CrossAccountId,1080 to: T::CrossAccountId,1081 data: CreateItemData,1082 nesting_budget: &dyn Budget,1083 ) -> DispatchResultWithPostInfo;1084 fn create_multiple_items(1085 &self,1086 sender: T::CrossAccountId,1087 to: T::CrossAccountId,1088 data: Vec<CreateItemData>,1089 nesting_budget: &dyn Budget,1090 ) -> DispatchResultWithPostInfo;1091 fn create_multiple_items_ex(1092 &self,1093 sender: T::CrossAccountId,1094 data: CreateItemExData<T::CrossAccountId>,1095 nesting_budget: &dyn Budget,1096 ) -> DispatchResultWithPostInfo;1097 fn burn_item(1098 &self,1099 sender: T::CrossAccountId,1100 token: TokenId,1101 amount: u128,1102 ) -> DispatchResultWithPostInfo;1103 fn set_collection_properties(1104 &self,1105 sender: T::CrossAccountId,1106 properties: Vec<Property>,1107 ) -> DispatchResultWithPostInfo;1108 fn delete_collection_properties(1109 &self,1110 sender: &T::CrossAccountId,1111 property_keys: Vec<PropertyKey>,1112 ) -> DispatchResultWithPostInfo;1113 fn set_token_properties(1114 &self,1115 sender: T::CrossAccountId,1116 token_id: TokenId,1117 property: Vec<Property>,1118 ) -> DispatchResultWithPostInfo;1119 fn delete_token_properties(1120 &self,1121 sender: T::CrossAccountId,1122 token_id: TokenId,1123 property_keys: Vec<PropertyKey>,1124 ) -> DispatchResultWithPostInfo;1125 fn set_property_permissions(1126 &self,1127 sender: &T::CrossAccountId,1128 property_permissions: Vec<PropertyKeyPermission>,1129 ) -> DispatchResultWithPostInfo;1130 fn transfer(1131 &self,1132 sender: T::CrossAccountId,1133 to: T::CrossAccountId,1134 token: TokenId,1135 amount: u128,1136 nesting_budget: &dyn Budget,1137 ) -> DispatchResultWithPostInfo;1138 fn approve(1139 &self,1140 sender: T::CrossAccountId,1141 spender: T::CrossAccountId,1142 token: TokenId,1143 amount: u128,1144 ) -> DispatchResultWithPostInfo;1145 fn transfer_from(1146 &self,1147 sender: T::CrossAccountId,1148 from: T::CrossAccountId,1149 to: T::CrossAccountId,1150 token: TokenId,1151 amount: u128,1152 nesting_budget: &dyn Budget,1153 ) -> DispatchResultWithPostInfo;1154 fn burn_from(1155 &self,1156 sender: T::CrossAccountId,1157 from: T::CrossAccountId,1158 token: TokenId,1159 amount: u128,1160 nesting_budget: &dyn Budget,1161 ) -> DispatchResultWithPostInfo;11621163 fn check_nesting(1164 &self,1165 sender: T::CrossAccountId,1166 from: (CollectionId, TokenId),1167 under: TokenId,1168 budget: &dyn Budget,1169 ) -> DispatchResult;11701171 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1172 fn collection_tokens(&self) -> Vec<TokenId>;1173 fn token_exists(&self, token: TokenId) -> bool;1174 fn last_token_id(&self) -> TokenId;11751176 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1177 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1178 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1179 1180 fn total_supply(&self) -> u32;1181 1182 fn account_balance(&self, account: T::CrossAccountId) -> u32;1183 1184 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1185 fn allowance(1186 &self,1187 sender: T::CrossAccountId,1188 spender: T::CrossAccountId,1189 token: TokenId,1190 ) -> u128;1191}119211931194pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1195 let post_info = PostDispatchInfo {1196 actual_weight: Some(weight),1197 pays_fee: Pays::Yes,1198 };1199 match res {1200 Ok(()) => Ok(post_info),1201 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1202 }1203}12041205impl<T: Config> From<PropertiesError> for Error<T> {1206 fn from(error: PropertiesError) -> Self {1207 match error {1208 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1209 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1210 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1211 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1212 }1213 }1214}