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,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29 transactional,30};31use pallet_evm::GasWeightMapping;32use up_data_structs::{33 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,34 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId, CollectionStats,35 MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,36 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,37 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData,38 SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField, PhantomType,39 Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,40 PropertiesError, PropertyKeyPermission, TokenData, TrySetProperty,41};42pub use pallet::*;43use sp_core::H160;44use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};45#[cfg(feature = "runtime-benchmarks")]46pub mod benchmarking;47pub mod dispatch;48pub mod erc;49pub mod eth;5051#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]52pub struct CollectionHandle<T: Config> {53 pub id: CollectionId,54 collection: Collection<T::AccountId>,55 pub recorder: SubstrateRecorder<T>,56}57impl<T: Config> WithRecorder<T> for CollectionHandle<T> {58 fn recorder(&self) -> &SubstrateRecorder<T> {59 &self.recorder60 }61 fn into_recorder(self) -> SubstrateRecorder<T> {62 self.recorder63 }64}65impl<T: Config> CollectionHandle<T> {66 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {67 <CollectionById<T>>::get(id).map(|collection| Self {68 id,69 collection,70 recorder: SubstrateRecorder::new(gas_limit),71 })72 }73 pub fn new(id: CollectionId) -> Option<Self> {74 Self::new_with_gas_limit(id, u64::MAX)75 }76 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {77 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)78 }79 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {80 self.recorder81 .consume_gas(T::GasWeightMapping::weight_to_gas(82 <T as frame_system::Config>::DbWeight::get()83 .read84 .saturating_mul(reads),85 ))86 }87 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {88 self.recorder89 .consume_gas(T::GasWeightMapping::weight_to_gas(90 <T as frame_system::Config>::DbWeight::get()91 .write92 .saturating_mul(writes),93 ))94 }95 pub fn save(self) -> DispatchResult {96 <CollectionById<T>>::insert(self.id, self.collection);97 Ok(())98 }99}100impl<T: Config> Deref for CollectionHandle<T> {101 type Target = Collection<T::AccountId>;102103 fn deref(&self) -> &Self::Target {104 &self.collection105 }106}107108impl<T: Config> DerefMut for CollectionHandle<T> {109 fn deref_mut(&mut self) -> &mut Self::Target {110 &mut self.collection111 }112}113114impl<T: Config> CollectionHandle<T> {115 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {116 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);117 Ok(())118 }119 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {120 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))121 }122 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {123 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);124 Ok(())125 }126 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {127 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)128 }129 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {130 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)131 }132 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {133 ensure!(134 <Allowlist<T>>::get((self.id, user)),135 <Error<T>>::AddressNotInAllowlist136 );137 Ok(())138 }139}140141#[frame_support::pallet]142pub mod pallet {143 use super::*;144 use pallet_evm::account;145 use dispatch::CollectionDispatch;146 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};147 use frame_system::pallet_prelude::*;148 use frame_support::traits::Currency;149 use up_data_structs::{TokenId, mapping::TokenAddressMapping};150 use scale_info::TypeInfo;151152 #[pallet::config]153 pub trait Config:154 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config155 {156 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;157158 type Currency: Currency<Self::AccountId>;159160 #[pallet::constant]161 type CollectionCreationPrice: Get<162 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,163 >;164 type CollectionDispatch: CollectionDispatch<Self>;165166 type TreasuryAccountId: Get<Self::AccountId>;167168 type EvmTokenAddressMapping: TokenAddressMapping<H160>;169 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;170 }171172 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);173174 #[pallet::pallet]175 #[pallet::storage_version(STORAGE_VERSION)]176 #[pallet::generate_store(pub(super) trait Store)]177 pub struct Pallet<T>(_);178179 #[pallet::extra_constants]180 impl<T: Config> Pallet<T> {181 pub fn collection_admins_limit() -> u32 {182 COLLECTION_ADMINS_LIMIT183 }184 }185186 #[pallet::event]187 #[pallet::generate_deposit(pub fn deposit_event)]188 pub enum Event<T: Config> {189 190 191 192 193 194 195 196 197 198 CollectionCreated(CollectionId, u8, T::AccountId),199200 201 202 203 204 205 CollectionDestroyed(CollectionId),206207 208 209 210 211 212 213 214 215 216 217 218 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),219220 221 222 223 224 225 226 227 228 229 230 231 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),232233 234 235 236 237 238 239 240 241 242 243 244 Transfer(245 CollectionId,246 TokenId,247 T::CrossAccountId,248 T::CrossAccountId,249 u128,250 ),251252 253 254 255 256 257 258 259 260 261 Approved(262 CollectionId,263 TokenId,264 T::CrossAccountId,265 T::CrossAccountId,266 u128,267 ),268269 CollectionPropertySet(CollectionId, PropertyKey),270271 CollectionPropertyDeleted(CollectionId, PropertyKey),272273 TokenPropertySet(CollectionId, TokenId, PropertyKey),274275 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),276277 PropertyPermissionSet(CollectionId, PropertyKey),278 }279280 #[pallet::error]281 pub enum Error<T> {282 283 CollectionNotFound,284 285 MustBeTokenOwner,286 287 NoPermission,288 289 PublicMintingNotAllowed,290 291 AddressNotInAllowlist,292293 294 CollectionNameLimitExceeded,295 296 CollectionDescriptionLimitExceeded,297 298 CollectionTokenPrefixLimitExceeded,299 300 TotalCollectionsLimitExceeded,301 302 CollectionAdminCountExceeded,303 304 CollectionLimitBoundsExceeded,305 306 OwnerPermissionsCantBeReverted,307 308 TransferNotAllowed,309 310 AccountTokenLimitExceeded,311 312 CollectionTokenLimitExceeded,313 314 MetadataFlagFrozen,315316 317 TokenNotFound,318 319 TokenValueTooLow,320 321 ApprovedValueTooLow,322 323 CantApproveMoreThanOwned,324325 326 AddressIsZero,327 328 UnsupportedOperation,329330 331 NotSufficientFounds,332333 334 NestingIsDisabled,335 336 OnlyOwnerAllowedToNest,337 338 SourceCollectionIsNotAllowedToNest,339340 341 CollectionFieldSizeExceeded,342343 344 NoSpaceForProperty,345346 347 PropertyLimitReached,348349 350 PropertyKeyIsTooLong,351352 353 InvalidCharacterInPropertyKey,354355 356 EmptyPropertyKey,357 }358359 #[pallet::storage]360 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;361 #[pallet::storage]362 pub type DestroyedCollectionCount<T> =363 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;364365 366 #[pallet::storage]367 pub type CollectionById<T> = StorageMap<368 Hasher = Blake2_128Concat,369 Key = CollectionId,370 Value = Collection<<T as frame_system::Config>::AccountId>,371 QueryKind = OptionQuery,372 >;373374 375 #[pallet::storage]376 #[pallet::getter(fn collection_properties)]377 pub type CollectionProperties<T> = StorageMap<378 Hasher = Blake2_128Concat,379 Key = CollectionId,380 Value = Properties,381 QueryKind = ValueQuery,382 OnEmpty = up_data_structs::CollectionProperties,383 >;384385 #[pallet::storage]386 #[pallet::getter(fn property_permissions)]387 pub type CollectionPropertyPermissions<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = PropertiesPermissionMap,391 QueryKind = ValueQuery,392 >;393394 395 #[pallet::storage]396 pub type CollectionData<T> = StorageNMap<397 Key = (398 Key<Twox64Concat, CollectionId>,399 Key<Twox64Concat, CollectionField>,400 ),401 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,402 QueryKind = ValueQuery,403 >;404405 #[pallet::storage]406 pub type AdminAmount<T> = StorageMap<407 Hasher = Blake2_128Concat,408 Key = CollectionId,409 Value = u32,410 QueryKind = ValueQuery,411 >;412413 414 #[pallet::storage]415 pub type IsAdmin<T: Config> = StorageNMap<416 Key = (417 Key<Blake2_128Concat, CollectionId>,418 Key<Blake2_128Concat, T::CrossAccountId>,419 ),420 Value = bool,421 QueryKind = ValueQuery,422 >;423424 425 #[pallet::storage]426 pub type Allowlist<T: Config> = StorageNMap<427 Key = (428 Key<Blake2_128Concat, CollectionId>,429 Key<Blake2_128Concat, T::CrossAccountId>,430 ),431 Value = bool,432 QueryKind = ValueQuery,433 >;434435 436 #[pallet::storage]437 pub type DummyStorageValue<T: Config> = StorageValue<438 Value = (439 CollectionStats,440 CollectionId,441 TokenId,442 PhantomType<TokenData<T::CrossAccountId>>,443 PhantomType<RpcCollection<T::AccountId>>,444 ),445 QueryKind = OptionQuery,446 >;447448 #[pallet::hooks]449 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {450 fn on_runtime_upgrade() -> Weight {451 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {452 use up_data_structs::{CollectionVersion1, CollectionVersion2};453 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {454 Self::set_field_raw(455 id,456 CollectionField::OffchainSchema,457 v.offchain_schema.clone().into_inner(),458 )459 .expect("data has lower bounds than field");460 Self::set_field_raw(461 id,462 CollectionField::ConstOnChainSchema,463 v.const_on_chain_schema.clone().into_inner(),464 )465 .expect("data has lower bounds than field");466467 Some(CollectionVersion2::from(v))468 });469 }470471 0472 }473 }474}475476impl<T: Config> Pallet<T> {477 478 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {479 ensure!(480 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,481 <Error<T>>::AddressIsZero482 );483 Ok(())484 }485 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {486 <IsAdmin<T>>::iter_prefix((collection,))487 .map(|(a, _)| a)488 .collect()489 }490 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {491 <Allowlist<T>>::iter_prefix((collection,))492 .map(|(a, _)| a)493 .collect()494 }495 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {496 <Allowlist<T>>::get((collection, user))497 }498 pub fn collection_stats() -> CollectionStats {499 let created = <CreatedCollectionCount<T>>::get();500 let destroyed = <DestroyedCollectionCount<T>>::get();501 CollectionStats {502 created: created.0,503 destroyed: destroyed.0,504 alive: created.0 - destroyed.0,505 }506 }507508 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {509 let collection = <CollectionById<T>>::get(collection);510 if collection.is_none() {511 return None;512 }513514 let collection = collection.unwrap();515 let limits = collection.limits;516 let effective_limits = CollectionLimits {517 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),518 sponsored_data_size: Some(limits.sponsored_data_size()),519 sponsored_data_rate_limit: Some(520 limits521 .sponsored_data_rate_limit522 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),523 ),524 token_limit: Some(limits.token_limit()),525 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(526 match collection.mode {527 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,528 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,529 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,530 },531 )),532 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),533 owner_can_transfer: Some(limits.owner_can_transfer()),534 owner_can_destroy: Some(limits.owner_can_destroy()),535 transfers_enabled: Some(limits.transfers_enabled()),536 nesting_rule: Some(limits.nesting_rule().clone()),537 };538539 Some(effective_limits)540 }541542 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {543 let Collection {544 name,545 description,546 owner,547 mode,548 access,549 token_prefix,550 mint_mode,551 schema_version,552 sponsorship,553 limits,554 } = <CollectionById<T>>::get(collection)?;555556 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)557 .iter()558 .map(|(key, permission)| PropertyKeyPermission {559 key: key.clone(),560 permission: permission.clone(),561 })562 .collect();563564 let properties = <CollectionProperties<T>>::get(collection)565 .iter()566 .map(|(key, value)| Property {567 key: key.clone(),568 value: value.clone(),569 })570 .collect();571572 Some(RpcCollection {573 name: name.into_inner(),574 description: description.into_inner(),575 owner,576 mode,577 access,578 token_prefix: token_prefix.into_inner(),579 mint_mode,580 schema_version,581 sponsorship,582 limits,583 offchain_schema: <CollectionData<T>>::get((584 collection,585 CollectionField::OffchainSchema,586 ))587 .into_inner(),588 const_on_chain_schema: <CollectionData<T>>::get((589 collection,590 CollectionField::ConstOnChainSchema,591 ))592 .into_inner(),593 token_property_permissions,594 properties,595 })596 }597}598599impl<T: Config> Pallet<T> {600 pub fn init_collection(601 owner: T::AccountId,602 data: CreateCollectionData<T::AccountId>,603 ) -> Result<CollectionId, DispatchError> {604 {605 ensure!(606 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,607 Error::<T>::CollectionTokenPrefixLimitExceeded608 );609 }610611 let created_count = <CreatedCollectionCount<T>>::get()612 .0613 .checked_add(1)614 .ok_or(ArithmeticError::Overflow)?;615 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;616 let id = CollectionId(created_count);617618 619 ensure!(620 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,621 <Error<T>>::TotalCollectionsLimitExceeded622 );623624 625626 let collection = Collection {627 owner: owner.clone(),628 name: data.name,629 mode: data.mode.clone(),630 mint_mode: false,631 access: data.access.unwrap_or_default(),632 description: data.description,633 token_prefix: data.token_prefix,634 schema_version: data.schema_version.unwrap_or_default(),635 sponsorship: data636 .pending_sponsor637 .map(SponsorshipState::Unconfirmed)638 .unwrap_or_default(),639 limits: data640 .limits641 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))642 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,643 };644645 let mut collection_properties = up_data_structs::CollectionProperties::get();646 collection_properties647 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))648 .map_err(<Error<T>>::from)?;649650 CollectionProperties::<T>::insert(id, collection_properties);651652 let mut token_props_permissions = PropertiesPermissionMap::new();653 token_props_permissions654 .try_set_from_iter(655 data.token_property_permissions656 .into_iter()657 .map(|property| (property.key, property.permission)),658 )659 .map_err(<Error<T>>::from)?;660661 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);662663 664 {665 let mut imbalance =666 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();667 imbalance.subsume(668 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(669 &T::TreasuryAccountId::get(),670 T::CollectionCreationPrice::get(),671 ),672 );673 <T as Config>::Currency::settle(674 &owner,675 imbalance,676 WithdrawReasons::TRANSFER,677 ExistenceRequirement::KeepAlive,678 )679 .map_err(|_| Error::<T>::NotSufficientFounds)?;680 }681682 <CreatedCollectionCount<T>>::put(created_count);683 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));684 <CollectionById<T>>::insert(id, collection);685 Self::set_field_raw(686 id,687 CollectionField::OffchainSchema,688 data.offchain_schema.into_inner(),689 )690 .expect("data has lower bounds than field");691 Self::set_field_raw(692 id,693 CollectionField::ConstOnChainSchema,694 data.const_on_chain_schema.into_inner(),695 )696 .expect("data has lower bounds than field");697 Ok(id)698 }699700 pub fn destroy_collection(701 collection: CollectionHandle<T>,702 sender: &T::CrossAccountId,703 ) -> DispatchResult {704 ensure!(705 collection.limits.owner_can_destroy(),706 <Error<T>>::NoPermission,707 );708 collection.check_is_owner(sender)?;709710 let destroyed_collections = <DestroyedCollectionCount<T>>::get()711 .0712 .checked_add(1)713 .ok_or(ArithmeticError::Overflow)?;714715 716717 <DestroyedCollectionCount<T>>::put(destroyed_collections);718 <CollectionById<T>>::remove(collection.id);719 <CollectionData<T>>::remove_prefix((collection.id,), None);720 <AdminAmount<T>>::remove(collection.id);721 <IsAdmin<T>>::remove_prefix((collection.id,), None);722 <Allowlist<T>>::remove_prefix((collection.id,), None);723724 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));725 Ok(())726 }727728 pub fn set_collection_property(729 collection: &CollectionHandle<T>,730 sender: &T::CrossAccountId,731 property: Property,732 ) -> DispatchResult {733 collection.check_is_owner_or_admin(sender)?;734735 CollectionProperties::<T>::try_mutate(collection.id, |properties| {736 let property = property.clone();737 properties.try_set(property.key, property.value)738 })739 .map_err(<Error<T>>::from)?;740741 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));742743 Ok(())744 }745746 #[transactional]747 pub fn set_collection_properties(748 collection: &CollectionHandle<T>,749 sender: &T::CrossAccountId,750 properties: Vec<Property>,751 ) -> DispatchResult {752 for property in properties {753 Self::set_collection_property(collection, sender, property)?;754 }755756 Ok(())757 }758759 pub fn delete_collection_property(760 collection: &CollectionHandle<T>,761 sender: &T::CrossAccountId,762 property_key: PropertyKey,763 ) -> DispatchResult {764 collection.check_is_owner_or_admin(sender)?;765766 CollectionProperties::<T>::try_mutate(collection.id, |properties| {767 properties.remove(&property_key)768 })769 .map_err(<Error<T>>::from)?;770771 Self::deposit_event(Event::CollectionPropertyDeleted(772 collection.id,773 property_key,774 ));775776 Ok(())777 }778779 #[transactional]780 pub fn delete_collection_properties(781 collection: &CollectionHandle<T>,782 sender: &T::CrossAccountId,783 property_keys: Vec<PropertyKey>,784 ) -> DispatchResult {785 for key in property_keys {786 Self::delete_collection_property(collection, sender, key)?;787 }788789 Ok(())790 }791792 pub fn set_property_permission(793 collection: &CollectionHandle<T>,794 sender: &T::CrossAccountId,795 property_permission: PropertyKeyPermission,796 ) -> DispatchResult {797 collection.check_is_owner_or_admin(sender)?;798799 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);800 let current_permission = all_permissions.get(&property_permission.key);801 if matches![802 current_permission,803 Some(PropertyPermission { mutable: false, .. })804 ] {805 return Err(<Error<T>>::NoPermission.into());806 }807808 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {809 let property_permission = property_permission.clone();810 permissions.try_set(property_permission.key, property_permission.permission)811 })812 .map_err(<Error<T>>::from)?;813814 Self::deposit_event(Event::PropertyPermissionSet(815 collection.id,816 property_permission.key,817 ));818819 Ok(())820 }821822 #[transactional]823 pub fn set_property_permissions(824 collection: &CollectionHandle<T>,825 sender: &T::CrossAccountId,826 property_permissions: Vec<PropertyKeyPermission>,827 ) -> DispatchResult {828 for prop_pemission in property_permissions {829 Self::set_property_permission(collection, sender, prop_pemission)?;830 }831832 Ok(())833 }834835 pub fn bytes_keys_to_property_keys(836 keys: Vec<Vec<u8>>,837 ) -> Result<Vec<PropertyKey>, DispatchError> {838 keys.into_iter()839 .map(|key| -> Result<PropertyKey, DispatchError> {840 key.try_into()841 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())842 })843 .collect::<Result<Vec<PropertyKey>, DispatchError>>()844 }845846 pub fn filter_collection_properties(847 collection_id: CollectionId,848 keys: Option<Vec<PropertyKey>>,849 ) -> Result<Vec<Property>, DispatchError> {850 let properties = Self::collection_properties(collection_id);851852 let properties = keys.map(|keys| {853 keys.into_iter()854 .filter_map(|key| {855 properties.get(&key).map(|value| Property {856 key,857 value: value.clone(),858 })859 })860 .collect()861 }).unwrap_or_else(862 || properties.iter()863 .map(|(key, value)| Property {864 key: key.clone(),865 value: value.clone(),866 })867 .collect()868 );869870 Ok(properties)871 }872873 pub fn filter_property_permissions(874 collection_id: CollectionId,875 keys: Option<Vec<PropertyKey>>,876 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {877 let permissions = Self::property_permissions(collection_id);878879 let key_permissions = keys.map(|keys| {880 keys.into_iter()881 .filter_map(|key| {882 permissions883 .get(&key)884 .map(|permission| PropertyKeyPermission {885 key,886 permission: permission.clone(),887 })888 })889 .collect()890 }).unwrap_or_else(891 || permissions.iter()892 .map(|(key, permission)| PropertyKeyPermission {893 key: key.clone(),894 permission: permission.clone(),895 })896 .collect()897 );898899 Ok(key_permissions)900 }901902 fn set_field_raw(903 collection_id: CollectionId,904 field: CollectionField,905 value: Vec<u8>,906 ) -> DispatchResult {907 if !value.is_empty() {908 <CollectionData<T>>::insert(909 (collection_id, field),910 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,911 )912 } else {913 <CollectionData<T>>::remove((collection_id, field));914 }915 Ok(())916 }917918 pub fn set_field(919 collection: &CollectionHandle<T>,920 sender: &T::CrossAccountId,921 field: CollectionField,922 value: Vec<u8>,923 ) -> DispatchResult {924 collection.check_is_owner_or_admin(sender)?;925926 927928 Self::set_field_raw(collection.id, field, value)929 }930931 pub fn toggle_allowlist(932 collection: &CollectionHandle<T>,933 sender: &T::CrossAccountId,934 user: &T::CrossAccountId,935 allowed: bool,936 ) -> DispatchResult {937 collection.check_is_owner_or_admin(sender)?;938939 940941 if allowed {942 <Allowlist<T>>::insert((collection.id, user), true);943 } else {944 <Allowlist<T>>::remove((collection.id, user));945 }946947 Ok(())948 }949950 pub fn toggle_admin(951 collection: &CollectionHandle<T>,952 sender: &T::CrossAccountId,953 user: &T::CrossAccountId,954 admin: bool,955 ) -> DispatchResult {956 collection.check_is_owner_or_admin(sender)?;957958 let was_admin = <IsAdmin<T>>::get((collection.id, user));959 if was_admin == admin {960 return Ok(());961 }962 let amount = <AdminAmount<T>>::get(collection.id);963964 if admin {965 let amount = amount966 .checked_add(1)967 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;968 ensure!(969 amount <= Self::collection_admins_limit(),970 <Error<T>>::CollectionAdminCountExceeded,971 );972973 974975 <AdminAmount<T>>::insert(collection.id, amount);976 <IsAdmin<T>>::insert((collection.id, user), true);977 } else {978 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));979 <IsAdmin<T>>::remove((collection.id, user));980 }981982 Ok(())983 }984985 pub fn clamp_limits(986 mode: CollectionMode,987 old_limit: &CollectionLimits,988 mut new_limit: CollectionLimits,989 ) -> Result<CollectionLimits, DispatchError> {990 macro_rules! limit_default {991 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{992 $(993 if let Some($new) = $new.$field {994 let $old = $old.$field($($arg)?);995 let _ = $new;996 let _ = $old;997 $check998 } else {999 $new.$field = $old.$field1000 }1001 )*1002 }};1003 }10041005 limit_default!(old_limit, new_limit,1006 account_token_ownership_limit => ensure!(1007 new_limit <= MAX_TOKEN_OWNERSHIP,1008 <Error<T>>::CollectionLimitBoundsExceeded,1009 ),1010 sponsor_transfer_timeout(match mode {1011 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1012 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1013 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1014 }) => ensure!(1015 new_limit <= MAX_SPONSOR_TIMEOUT,1016 <Error<T>>::CollectionLimitBoundsExceeded,1017 ),1018 sponsored_data_size => ensure!(1019 new_limit <= CUSTOM_DATA_LIMIT,1020 <Error<T>>::CollectionLimitBoundsExceeded,1021 ),1022 token_limit => ensure!(1023 old_limit >= new_limit && new_limit > 0,1024 <Error<T>>::CollectionTokenLimitExceeded1025 ),1026 owner_can_transfer => ensure!(1027 old_limit || !new_limit,1028 <Error<T>>::OwnerPermissionsCantBeReverted,1029 ),1030 owner_can_destroy => ensure!(1031 old_limit || !new_limit,1032 <Error<T>>::OwnerPermissionsCantBeReverted,1033 ),1034 sponsored_data_rate_limit => {},1035 transfers_enabled => {},1036 );1037 Ok(new_limit)1038 }1039}10401041#[macro_export]1042macro_rules! unsupported {1043 () => {1044 Err(<Error<T>>::UnsupportedOperation.into())1045 };1046}104710481049pub trait CommonWeightInfo<CrossAccountId> {1050 fn create_item() -> Weight;1051 fn create_multiple_items(amount: u32) -> Weight;1052 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1053 fn burn_item() -> Weight;1054 fn set_collection_properties(amount: u32) -> Weight;1055 fn delete_collection_properties(amount: u32) -> Weight;1056 fn set_token_properties(amount: u32) -> Weight;1057 fn delete_token_properties(amount: u32) -> Weight;1058 fn set_property_permissions(amount: u32) -> Weight;1059 fn transfer() -> Weight;1060 fn approve() -> Weight;1061 fn transfer_from() -> Weight;1062 fn burn_from() -> Weight;1063}10641065pub trait CommonCollectionOperations<T: Config> {1066 fn create_item(1067 &self,1068 sender: T::CrossAccountId,1069 to: T::CrossAccountId,1070 data: CreateItemData,1071 nesting_budget: &dyn Budget,1072 ) -> DispatchResultWithPostInfo;1073 fn create_multiple_items(1074 &self,1075 sender: T::CrossAccountId,1076 to: T::CrossAccountId,1077 data: Vec<CreateItemData>,1078 nesting_budget: &dyn Budget,1079 ) -> DispatchResultWithPostInfo;1080 fn create_multiple_items_ex(1081 &self,1082 sender: T::CrossAccountId,1083 data: CreateItemExData<T::CrossAccountId>,1084 nesting_budget: &dyn Budget,1085 ) -> DispatchResultWithPostInfo;1086 fn burn_item(1087 &self,1088 sender: T::CrossAccountId,1089 token: TokenId,1090 amount: u128,1091 ) -> DispatchResultWithPostInfo;1092 fn set_collection_properties(1093 &self,1094 sender: T::CrossAccountId,1095 properties: Vec<Property>,1096 ) -> DispatchResultWithPostInfo;1097 fn delete_collection_properties(1098 &self,1099 sender: &T::CrossAccountId,1100 property_keys: Vec<PropertyKey>,1101 ) -> DispatchResultWithPostInfo;1102 fn set_token_properties(1103 &self,1104 sender: T::CrossAccountId,1105 token_id: TokenId,1106 property: Vec<Property>,1107 ) -> DispatchResultWithPostInfo;1108 fn delete_token_properties(1109 &self,1110 sender: T::CrossAccountId,1111 token_id: TokenId,1112 property_keys: Vec<PropertyKey>,1113 ) -> DispatchResultWithPostInfo;1114 fn set_property_permissions(1115 &self,1116 sender: &T::CrossAccountId,1117 property_permissions: Vec<PropertyKeyPermission>,1118 ) -> DispatchResultWithPostInfo;1119 fn transfer(1120 &self,1121 sender: T::CrossAccountId,1122 to: T::CrossAccountId,1123 token: TokenId,1124 amount: u128,1125 nesting_budget: &dyn Budget,1126 ) -> DispatchResultWithPostInfo;1127 fn approve(1128 &self,1129 sender: T::CrossAccountId,1130 spender: T::CrossAccountId,1131 token: TokenId,1132 amount: u128,1133 ) -> DispatchResultWithPostInfo;1134 fn transfer_from(1135 &self,1136 sender: T::CrossAccountId,1137 from: T::CrossAccountId,1138 to: T::CrossAccountId,1139 token: TokenId,1140 amount: u128,1141 nesting_budget: &dyn Budget,1142 ) -> DispatchResultWithPostInfo;1143 fn burn_from(1144 &self,1145 sender: T::CrossAccountId,1146 from: T::CrossAccountId,1147 token: TokenId,1148 amount: u128,1149 nesting_budget: &dyn Budget,1150 ) -> DispatchResultWithPostInfo;11511152 fn check_nesting(1153 &self,1154 sender: T::CrossAccountId,1155 from: (CollectionId, TokenId),1156 under: TokenId,1157 budget: &dyn Budget,1158 ) -> DispatchResult;11591160 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1161 fn collection_tokens(&self) -> Vec<TokenId>;1162 fn token_exists(&self, token: TokenId) -> bool;1163 fn last_token_id(&self) -> TokenId;11641165 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1166 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1167 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1168 1169 fn total_supply(&self) -> u32;1170 1171 fn account_balance(&self, account: T::CrossAccountId) -> u32;1172 1173 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1174 fn allowance(1175 &self,1176 sender: T::CrossAccountId,1177 spender: T::CrossAccountId,1178 token: TokenId,1179 ) -> u128;1180}118111821183pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1184 let post_info = PostDispatchInfo {1185 actual_weight: Some(weight),1186 pays_fee: Pays::Yes,1187 };1188 match res {1189 Ok(()) => Ok(post_info),1190 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1191 }1192}11931194impl<T: Config> From<PropertiesError> for Error<T> {1195 fn from(error: PropertiesError) -> Self {1196 match error {1197 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1198 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1199 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1200 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1201 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1202 }1203 }1204}