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, collections::btree_map::BTreeMap};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,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(eth::collection_id_to_address(id), 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 log_mirrored(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_mirrored(log)80 }81 pub fn log_direct(&self, log: impl evm_coder::ToLog) {82 self.recorder.log_direct(log)83 }84 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {85 self.recorder86 .consume_gas(T::GasWeightMapping::weight_to_gas(87 <T as frame_system::Config>::DbWeight::get()88 .read89 .saturating_mul(reads),90 ))91 }92 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {93 self.recorder94 .consume_gas(T::GasWeightMapping::weight_to_gas(95 <T as frame_system::Config>::DbWeight::get()96 .write97 .saturating_mul(writes),98 ))99 }100 pub fn submit_logs(self) {101 self.recorder.submit_logs()102 }103 pub fn save(self) -> DispatchResult {104 self.recorder.submit_logs();105 <CollectionById<T>>::insert(self.id, self.collection);106 Ok(())107 }108}109impl<T: Config> Deref for CollectionHandle<T> {110 type Target = Collection<T::AccountId>;111112 fn deref(&self) -> &Self::Target {113 &self.collection114 }115}116117impl<T: Config> DerefMut for CollectionHandle<T> {118 fn deref_mut(&mut self) -> &mut Self::Target {119 &mut self.collection120 }121}122123impl<T: Config> CollectionHandle<T> {124 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {125 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);126 Ok(())127 }128 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {129 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))130 }131 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {132 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);133 Ok(())134 }135 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {139 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)140 }141 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {142 ensure!(143 <Allowlist<T>>::get((self.id, user)),144 <Error<T>>::AddressNotInAllowlist145 );146 Ok(())147 }148149 pub fn check_can_update_meta(150 &self,151 subject: &T::CrossAccountId,152 item_owner: &T::CrossAccountId,153 ) -> DispatchResult {154 match self.meta_update_permission {155 MetaUpdatePermission::ItemOwner => {156 ensure!(subject == item_owner, <Error<T>>::NoPermission);157 Ok(())158 }159 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),160 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),161 }162 }163}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use pallet_evm::account;169 use dispatch::CollectionDispatch;170 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};171 use frame_system::pallet_prelude::*;172 use frame_support::traits::Currency;173 use up_data_structs::{TokenId, mapping::TokenAddressMapping};174 use scale_info::TypeInfo;175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config179 {180 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;181182 type Currency: Currency<Self::AccountId>;183184 #[pallet::constant]185 type CollectionCreationPrice: Get<186 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,187 >;188 type CollectionDispatch: CollectionDispatch<Self>;189190 type TreasuryAccountId: Get<Self::AccountId>;191192 type EvmTokenAddressMapping: TokenAddressMapping<H160>;193 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;194 }195196 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);197198 #[pallet::pallet]199 #[pallet::storage_version(STORAGE_VERSION)]200 #[pallet::generate_store(pub(super) trait Store)]201 pub struct Pallet<T>(_);202203 #[pallet::extra_constants]204 impl<T: Config> Pallet<T> {205 pub fn collection_admins_limit() -> u32 {206 COLLECTION_ADMINS_LIMIT207 }208 }209210 #[pallet::event]211 #[pallet::generate_deposit(pub fn deposit_event)]212 pub enum Event<T: Config> {213 214 215 216 217 218 219 220 221 222 CollectionCreated(CollectionId, u8, T::AccountId),223224 225 226 227 228 229 CollectionDestroyed(CollectionId),230231 232 233 234 235 236 237 238 239 240 241 242 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 Transfer(269 CollectionId,270 TokenId,271 T::CrossAccountId,272 T::CrossAccountId,273 u128,274 ),275276 277 278 279 280 281 282 283 284 285 Approved(286 CollectionId,287 TokenId,288 T::CrossAccountId,289 T::CrossAccountId,290 u128,291 ),292293 CollectionPropertySet(CollectionId, Property),294295 CollectionPropertyDeleted(CollectionId, PropertyKey),296297 TokenPropertySet(CollectionId, TokenId, Property),298299 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),300301 PropertyPermissionSet(CollectionId, PropertyKeyPermission),302 }303304 #[pallet::error]305 pub enum Error<T> {306 307 CollectionNotFound,308 309 MustBeTokenOwner,310 311 NoPermission,312 313 PublicMintingNotAllowed,314 315 AddressNotInAllowlist,316317 318 CollectionNameLimitExceeded,319 320 CollectionDescriptionLimitExceeded,321 322 CollectionTokenPrefixLimitExceeded,323 324 TotalCollectionsLimitExceeded,325 326 TokenVariableDataLimitExceeded,327 328 CollectionAdminCountExceeded,329 330 CollectionLimitBoundsExceeded,331 332 OwnerPermissionsCantBeReverted,333 334 TransferNotAllowed,335 336 AccountTokenLimitExceeded,337 338 CollectionTokenLimitExceeded,339 340 MetadataFlagFrozen,341342 343 TokenNotFound,344 345 TokenValueTooLow,346 347 ApprovedValueTooLow,348 349 CantApproveMoreThanOwned,350351 352 AddressIsZero,353 354 UnsupportedOperation,355356 357 NotSufficientFounds,358359 360 NestingIsDisabled,361 362 OnlyOwnerAllowedToNest,363 364 SourceCollectionIsNotAllowedToNest,365366 367 CollectionFieldSizeExceeded,368369 NoSpaceForProperty,370371 PropertyLimitReached,372 }373374 #[pallet::storage]375 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;376 #[pallet::storage]377 pub type DestroyedCollectionCount<T> =378 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;379380 381 #[pallet::storage]382 pub type CollectionById<T> = StorageMap<383 Hasher = Blake2_128Concat,384 Key = CollectionId,385 Value = Collection<<T as frame_system::Config>::AccountId>,386 QueryKind = OptionQuery,387 >;388389 390 #[pallet::storage]391 #[pallet::getter(fn collection_properties)]392 pub type CollectionProperties<T> = StorageMap<393 Hasher = Blake2_128Concat,394 Key = CollectionId,395 Value = Properties,396 QueryKind = ValueQuery,397 OnEmpty = up_data_structs::CollectionProperties,398 >;399400 #[pallet::storage]401 #[pallet::getter(fn property_permissions)]402 pub type CollectionPropertyPermissions<T> = StorageMap<403 Hasher = Blake2_128Concat,404 Key = CollectionId,405 Value = PropertiesPermissionMap,406 QueryKind = ValueQuery,407 >;408409 410 #[pallet::storage]411 pub type CollectionData<T> = StorageNMap<412 Key = (413 Key<Twox64Concat, CollectionId>,414 Key<Twox64Concat, CollectionField>,415 ),416 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,417 QueryKind = ValueQuery,418 >;419420 #[pallet::storage]421 pub type AdminAmount<T> = StorageMap<422 Hasher = Blake2_128Concat,423 Key = CollectionId,424 Value = u32,425 QueryKind = ValueQuery,426 >;427428 429 #[pallet::storage]430 pub type IsAdmin<T: Config> = StorageNMap<431 Key = (432 Key<Blake2_128Concat, CollectionId>,433 Key<Blake2_128Concat, T::CrossAccountId>,434 ),435 Value = bool,436 QueryKind = ValueQuery,437 >;438439 440 #[pallet::storage]441 pub type Allowlist<T: Config> = StorageNMap<442 Key = (443 Key<Blake2_128Concat, CollectionId>,444 Key<Blake2_128Concat, T::CrossAccountId>,445 ),446 Value = bool,447 QueryKind = ValueQuery,448 >;449450 451 #[pallet::storage]452 pub type DummyStorageValue<T: Config> = StorageValue<453 Value = (454 CollectionStats,455 CollectionId,456 TokenId,457 PhantomType<TokenData<T::CrossAccountId>>,458 PhantomType<RpcCollection<T::AccountId>>,459 ),460 QueryKind = OptionQuery,461 >;462463 #[pallet::hooks]464 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {465 fn on_runtime_upgrade() -> Weight {466 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {467 use up_data_structs::{CollectionVersion1, CollectionVersion2};468 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {469 Self::set_field_raw(470 id,471 CollectionField::OffchainSchema,472 v.offchain_schema.clone().into_inner(),473 )474 .expect("data has lower bounds than field");475 Self::set_field_raw(476 id,477 CollectionField::VariableOnChainSchema,478 v.variable_on_chain_schema.clone().into_inner(),479 )480 .expect("data has lower bounds than field");481 Self::set_field_raw(482 id,483 CollectionField::ConstOnChainSchema,484 v.const_on_chain_schema.clone().into_inner(),485 )486 .expect("data has lower bounds than field");487488 Some(CollectionVersion2::from(v))489 });490 }491492 0493 }494 }495}496497impl<T: Config> Pallet<T> {498 499 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {500 ensure!(501 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,502 <Error<T>>::AddressIsZero503 );504 Ok(())505 }506 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {507 <IsAdmin<T>>::iter_prefix((collection,))508 .map(|(a, _)| a)509 .collect()510 }511 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {512 <Allowlist<T>>::iter_prefix((collection,))513 .map(|(a, _)| a)514 .collect()515 }516 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {517 <Allowlist<T>>::get((collection, user))518 }519 pub fn collection_stats() -> CollectionStats {520 let created = <CreatedCollectionCount<T>>::get();521 let destroyed = <DestroyedCollectionCount<T>>::get();522 CollectionStats {523 created: created.0,524 destroyed: destroyed.0,525 alive: created.0 - destroyed.0,526 }527 }528529 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {530 let collection = <CollectionById<T>>::get(collection);531 if collection.is_none() {532 return None;533 }534535 let collection = collection.unwrap();536 let limits = collection.limits;537 let effective_limits = CollectionLimits {538 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),539 sponsored_data_size: Some(limits.sponsored_data_size()),540 sponsored_data_rate_limit: Some(541 limits542 .sponsored_data_rate_limit543 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),544 ),545 token_limit: Some(limits.token_limit()),546 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(547 match collection.mode {548 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,549 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,550 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,551 },552 )),553 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),554 owner_can_transfer: Some(limits.owner_can_transfer()),555 owner_can_destroy: Some(limits.owner_can_destroy()),556 transfers_enabled: Some(limits.transfers_enabled()),557 nesting_rule: Some(limits.nesting_rule().clone()),558 };559560 Some(effective_limits)561 }562563 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {564 let Collection {565 name,566 description,567 owner,568 mode,569 access,570 token_prefix,571 mint_mode,572 schema_version,573 sponsorship,574 limits,575 meta_update_permission,576 ..577 } = <CollectionById<T>>::get(collection)?;578 Some(RpcCollection {579 name: name.into_inner(),580 description: description.into_inner(),581 owner,582 mode,583 access,584 token_prefix: token_prefix.into_inner(),585 mint_mode,586 schema_version,587 sponsorship,588 limits,589 meta_update_permission,590 offchain_schema: <CollectionData<T>>::get((591 collection,592 CollectionField::OffchainSchema,593 ))594 .into_inner(),595 const_on_chain_schema: <CollectionData<T>>::get((596 collection,597 CollectionField::ConstOnChainSchema,598 ))599 .into_inner(),600 variable_on_chain_schema: <CollectionData<T>>::get((601 collection,602 CollectionField::VariableOnChainSchema,603 ))604 .into_inner(),605 })606 }607}608609impl<T: Config> Pallet<T> {610 pub fn init_collection(611 owner: T::AccountId,612 data: CreateCollectionData<T::AccountId>,613 ) -> Result<CollectionId, DispatchError> {614 {615 ensure!(616 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,617 Error::<T>::CollectionTokenPrefixLimitExceeded618 );619 }620621 let created_count = <CreatedCollectionCount<T>>::get()622 .0623 .checked_add(1)624 .ok_or(ArithmeticError::Overflow)?;625 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;626 let id = CollectionId(created_count);627628 629 ensure!(630 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,631 <Error<T>>::TotalCollectionsLimitExceeded632 );633634 635636 let collection = Collection {637 owner: owner.clone(),638 name: data.name,639 mode: data.mode.clone(),640 mint_mode: false,641 access: data.access.unwrap_or_default(),642 description: data.description,643 token_prefix: data.token_prefix,644 schema_version: data.schema_version.unwrap_or_default(),645 sponsorship: data646 .pending_sponsor647 .map(SponsorshipState::Unconfirmed)648 .unwrap_or_default(),649 limits: data650 .limits651 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))652 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,653 meta_update_permission: data.meta_update_permission.unwrap_or_default(),654 655 656 };657658 CollectionProperties::<T>::insert(659 id,660 Properties::from_collection_props_vec(data.properties)661 .map_err(|e| -> Error<T> { e.into() })?,662 );663664 let token_props_permissions: PropertiesPermissionMap = data665 .token_property_permissions666 .into_iter()667 .map(|property| (property.key, property.permission))668 .collect::<BTreeMap<_, _>>()669 .try_into()670 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;671672 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);673674 675 {676 let mut imbalance =677 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();678 imbalance.subsume(679 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(680 &T::TreasuryAccountId::get(),681 T::CollectionCreationPrice::get(),682 ),683 );684 <T as Config>::Currency::settle(685 &owner,686 imbalance,687 WithdrawReasons::TRANSFER,688 ExistenceRequirement::KeepAlive,689 )690 .map_err(|_| Error::<T>::NotSufficientFounds)?;691 }692693 <CreatedCollectionCount<T>>::put(created_count);694 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));695 <CollectionById<T>>::insert(id, collection);696 Self::set_field_raw(697 id,698 CollectionField::OffchainSchema,699 data.offchain_schema.into_inner(),700 )701 .expect("data has lower bounds than field");702 Self::set_field_raw(703 id,704 CollectionField::VariableOnChainSchema,705 data.variable_on_chain_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 properties.try_set_property(property.clone())754 })755 .map_err(|e| -> Error<T> { e.into() })?;756757 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));758759 Ok(())760 }761762 pub fn set_collection_properties(763 collection: &CollectionHandle<T>,764 sender: &T::CrossAccountId,765 properties: Vec<Property>,766 ) -> DispatchResult {767 for property in properties {768 Self::set_collection_property(collection, sender, property)?;769 }770771 Ok(())772 }773774 pub fn delete_collection_property(775 collection: &CollectionHandle<T>,776 sender: &T::CrossAccountId,777 property_key: PropertyKey,778 ) -> DispatchResult {779 collection.check_is_owner_or_admin(sender)?;780781 CollectionProperties::<T>::mutate(collection.id, |properties| {782 properties.remove_property(&property_key);783 });784785 Self::deposit_event(Event::CollectionPropertyDeleted(786 collection.id,787 property_key,788 ));789790 Ok(())791 }792793 pub fn delete_collection_properties(794 collection: &CollectionHandle<T>,795 sender: &T::CrossAccountId,796 property_keys: Vec<PropertyKey>,797 ) -> DispatchResult {798 for key in property_keys {799 Self::delete_collection_property(collection, sender, key)?;800 }801802 Ok(())803 }804805 pub fn set_property_permission(806 collection: &CollectionHandle<T>,807 sender: &T::CrossAccountId,808 property_permission: PropertyKeyPermission,809 ) -> DispatchResult {810 collection.check_is_owner_or_admin(sender)?;811812 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);813 let current_permission = all_permissions.get(&property_permission.key);814 if matches![815 current_permission,816 Some(PropertyPermission { mutable: false, .. })817 ] {818 return Err(<Error<T>>::NoPermission.into());819 }820821 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {822 let property_permission = property_permission.clone();823 permissions.try_insert(property_permission.key, property_permission.permission)824 })825 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;826827 Self::deposit_event(Event::PropertyPermissionSet(828 collection.id,829 property_permission,830 ));831832 Ok(())833 }834835 pub fn set_property_permissions(836 collection: &CollectionHandle<T>,837 sender: &T::CrossAccountId,838 property_permissions: Vec<PropertyKeyPermission>,839 ) -> DispatchResult {840 for prop_pemission in property_permissions {841 Self::set_property_permission(collection, sender, prop_pemission)?;842 }843844 Ok(())845 }846847 pub fn bytes_keys_to_property_keys(848 keys: Vec<Vec<u8>>,849 ) -> Result<Vec<PropertyKey>, DispatchError> {850 keys.into_iter()851 .map(|key| -> Result<PropertyKey, DispatchError> {852 853 key.try_into()854 .map_err(|_| DispatchError::Other("Can't read property key"))855 })856 .collect::<Result<Vec<PropertyKey>, DispatchError>>()857 }858859 pub fn filter_collection_properties(860 collection_id: CollectionId,861 keys: Vec<PropertyKey>,862 ) -> Result<Vec<Property>, DispatchError> {863 let properties = Self::collection_properties(collection_id);864865 let properties = keys866 .into_iter()867 .filter_map(|key| {868 properties.get_property(&key).map(|value| Property {869 key,870 value: value.clone(),871 })872 })873 .collect();874875 Ok(properties)876 }877878 pub fn filter_property_permissions(879 collection_id: CollectionId,880 keys: Vec<PropertyKey>,881 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {882 let permissions = Self::property_permissions(collection_id);883884 let key_permissions = keys885 .into_iter()886 .filter_map(|key| {887 permissions888 .get(&key)889 .map(|permission| PropertyKeyPermission {890 key,891 permission: permission.clone(),892 })893 })894 .collect();895896 Ok(key_permissions)897 }898899 fn set_field_raw(900 collection_id: CollectionId,901 field: CollectionField,902 value: Vec<u8>,903 ) -> DispatchResult {904 if !value.is_empty() {905 <CollectionData<T>>::insert(906 (collection_id, field),907 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,908 )909 } else {910 <CollectionData<T>>::remove((collection_id, field));911 }912 Ok(())913 }914915 pub fn set_field(916 collection: &CollectionHandle<T>,917 sender: &T::CrossAccountId,918 field: CollectionField,919 value: Vec<u8>,920 ) -> DispatchResult {921 collection.check_is_owner_or_admin(sender)?;922923 924925 Self::set_field_raw(collection.id, field, value)926 }927928 pub fn toggle_allowlist(929 collection: &CollectionHandle<T>,930 sender: &T::CrossAccountId,931 user: &T::CrossAccountId,932 allowed: bool,933 ) -> DispatchResult {934 collection.check_is_owner_or_admin(sender)?;935936 937938 if allowed {939 <Allowlist<T>>::insert((collection.id, user), true);940 } else {941 <Allowlist<T>>::remove((collection.id, user));942 }943944 Ok(())945 }946947 pub fn toggle_admin(948 collection: &CollectionHandle<T>,949 sender: &T::CrossAccountId,950 user: &T::CrossAccountId,951 admin: bool,952 ) -> DispatchResult {953 collection.check_is_owner_or_admin(sender)?;954955 let was_admin = <IsAdmin<T>>::get((collection.id, user));956 if was_admin == admin {957 return Ok(());958 }959 let amount = <AdminAmount<T>>::get(collection.id);960961 if admin {962 let amount = amount963 .checked_add(1)964 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;965 ensure!(966 amount <= Self::collection_admins_limit(),967 <Error<T>>::CollectionAdminCountExceeded,968 );969970 971972 <AdminAmount<T>>::insert(collection.id, amount);973 <IsAdmin<T>>::insert((collection.id, user), true);974 } else {975 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));976 <IsAdmin<T>>::remove((collection.id, user));977 }978979 Ok(())980 }981982 pub fn clamp_limits(983 mode: CollectionMode,984 old_limit: &CollectionLimits,985 mut new_limit: CollectionLimits,986 ) -> Result<CollectionLimits, DispatchError> {987 macro_rules! limit_default {988 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{989 $(990 if let Some($new) = $new.$field {991 let $old = $old.$field($($arg)?);992 let _ = $new;993 let _ = $old;994 $check995 } else {996 $new.$field = $old.$field997 }998 )*999 }};1000 }10011002 limit_default!(old_limit, new_limit,1003 account_token_ownership_limit => ensure!(1004 new_limit <= MAX_TOKEN_OWNERSHIP,1005 <Error<T>>::CollectionLimitBoundsExceeded,1006 ),1007 sponsor_transfer_timeout(match mode {1008 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1009 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1010 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1011 }) => ensure!(1012 new_limit <= MAX_SPONSOR_TIMEOUT,1013 <Error<T>>::CollectionLimitBoundsExceeded,1014 ),1015 sponsored_data_size => ensure!(1016 new_limit <= CUSTOM_DATA_LIMIT,1017 <Error<T>>::CollectionLimitBoundsExceeded,1018 ),1019 token_limit => ensure!(1020 old_limit >= new_limit && new_limit > 0,1021 <Error<T>>::CollectionTokenLimitExceeded1022 ),1023 owner_can_transfer => ensure!(1024 old_limit || !new_limit,1025 <Error<T>>::OwnerPermissionsCantBeReverted,1026 ),1027 owner_can_destroy => ensure!(1028 old_limit || !new_limit,1029 <Error<T>>::OwnerPermissionsCantBeReverted,1030 ),1031 sponsored_data_rate_limit => {},1032 transfers_enabled => {},1033 );1034 Ok(new_limit)1035 }1036}10371038#[macro_export]1039macro_rules! unsupported {1040 () => {1041 Err(<Error<T>>::UnsupportedOperation.into())1042 };1043}104410451046pub trait CommonWeightInfo<CrossAccountId> {1047 fn create_item() -> Weight;1048 fn create_multiple_items(amount: u32) -> Weight;1049 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1050 fn burn_item() -> Weight;1051 fn set_collection_properties(amount: u32) -> Weight;1052 fn delete_collection_properties(amount: u32) -> Weight;1053 fn set_token_properties(amount: u32) -> Weight;1054 fn delete_token_properties(amount: u32) -> Weight;1055 fn set_property_permissions(amount: u32) -> Weight;1056 fn transfer() -> Weight;1057 fn approve() -> Weight;1058 fn transfer_from() -> Weight;1059 fn burn_from() -> Weight;1060 fn set_variable_metadata(bytes: u32) -> Weight;1061}10621063pub trait CommonCollectionOperations<T: Config> {1064 fn create_item(1065 &self,1066 sender: T::CrossAccountId,1067 to: T::CrossAccountId,1068 data: CreateItemData,1069 nesting_budget: &dyn Budget,1070 ) -> DispatchResultWithPostInfo;1071 fn create_multiple_items(1072 &self,1073 sender: T::CrossAccountId,1074 to: T::CrossAccountId,1075 data: Vec<CreateItemData>,1076 nesting_budget: &dyn Budget,1077 ) -> DispatchResultWithPostInfo;1078 fn create_multiple_items_ex(1079 &self,1080 sender: T::CrossAccountId,1081 data: CreateItemExData<T::CrossAccountId>,1082 nesting_budget: &dyn Budget,1083 ) -> DispatchResultWithPostInfo;1084 fn burn_item(1085 &self,1086 sender: T::CrossAccountId,1087 token: TokenId,1088 amount: u128,1089 ) -> DispatchResultWithPostInfo;1090 fn set_collection_properties(1091 &self,1092 sender: T::CrossAccountId,1093 properties: Vec<Property>,1094 ) -> DispatchResultWithPostInfo;1095 fn delete_collection_properties(1096 &self,1097 sender: &T::CrossAccountId,1098 property_keys: Vec<PropertyKey>,1099 ) -> DispatchResultWithPostInfo;1100 fn set_token_properties(1101 &self,1102 sender: T::CrossAccountId,1103 token_id: TokenId,1104 property: Vec<Property>,1105 ) -> DispatchResultWithPostInfo;1106 fn delete_token_properties(1107 &self,1108 sender: T::CrossAccountId,1109 token_id: TokenId,1110 property_keys: Vec<PropertyKey>,1111 ) -> DispatchResultWithPostInfo;1112 fn set_property_permissions(1113 &self,1114 sender: &T::CrossAccountId,1115 property_permissions: Vec<PropertyKeyPermission>,1116 ) -> DispatchResultWithPostInfo;1117 fn transfer(1118 &self,1119 sender: T::CrossAccountId,1120 to: T::CrossAccountId,1121 token: TokenId,1122 amount: u128,1123 nesting_budget: &dyn Budget,1124 ) -> DispatchResultWithPostInfo;1125 fn approve(1126 &self,1127 sender: T::CrossAccountId,1128 spender: T::CrossAccountId,1129 token: TokenId,1130 amount: u128,1131 ) -> DispatchResultWithPostInfo;1132 fn transfer_from(1133 &self,1134 sender: T::CrossAccountId,1135 from: T::CrossAccountId,1136 to: T::CrossAccountId,1137 token: TokenId,1138 amount: u128,1139 nesting_budget: &dyn Budget,1140 ) -> DispatchResultWithPostInfo;1141 fn burn_from(1142 &self,1143 sender: T::CrossAccountId,1144 from: T::CrossAccountId,1145 token: TokenId,1146 amount: u128,1147 nesting_budget: &dyn Budget,1148 ) -> DispatchResultWithPostInfo;11491150 fn set_variable_metadata(1151 &self,1152 sender: T::CrossAccountId,1153 token: TokenId,1154 data: BoundedVec<u8, CustomDataLimit>,1155 ) -> DispatchResultWithPostInfo;11561157 fn check_nesting(1158 &self,1159 sender: T::CrossAccountId,1160 from: (CollectionId, TokenId),1161 under: TokenId,1162 budget: &dyn Budget,1163 ) -> DispatchResult;11641165 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1166 fn collection_tokens(&self) -> Vec<TokenId>;1167 fn token_exists(&self, token: TokenId) -> bool;1168 fn last_token_id(&self) -> TokenId;11691170 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1171 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1172 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1173 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1174 1175 fn total_supply(&self) -> u32;1176 1177 fn account_balance(&self, account: T::CrossAccountId) -> u32;1178 1179 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1180 fn allowance(1181 &self,1182 sender: T::CrossAccountId,1183 spender: T::CrossAccountId,1184 token: TokenId,1185 ) -> u128;1186}118711881189pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1190 let post_info = PostDispatchInfo {1191 actual_weight: Some(weight),1192 pays_fee: Pays::Yes,1193 };1194 match res {1195 Ok(()) => Ok(post_info),1196 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1197 }1198}11991200impl<T: Config> From<PropertiesError> for Error<T> {1201 fn from(error: PropertiesError) -> Self {1202 match error {1203 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1204 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1205 }1206 }1207}