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, CollectionPropertiesPermissionsVec,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 370 NoSpaceForProperty,371372 373 PropertyLimitReached,374375 376 UnableToReadUnboundedKeys,377 }378379 #[pallet::storage]380 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;381 #[pallet::storage]382 pub type DestroyedCollectionCount<T> =383 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;384385 386 #[pallet::storage]387 pub type CollectionById<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = Collection<<T as frame_system::Config>::AccountId>,391 QueryKind = OptionQuery,392 >;393394 395 #[pallet::storage]396 #[pallet::getter(fn collection_properties)]397 pub type CollectionProperties<T> = StorageMap<398 Hasher = Blake2_128Concat,399 Key = CollectionId,400 Value = Properties,401 QueryKind = ValueQuery,402 OnEmpty = up_data_structs::CollectionProperties,403 >;404405 #[pallet::storage]406 #[pallet::getter(fn property_permissions)]407 pub type CollectionPropertyPermissions<T> = StorageMap<408 Hasher = Blake2_128Concat,409 Key = CollectionId,410 Value = PropertiesPermissionMap,411 QueryKind = ValueQuery,412 >;413414 415 #[pallet::storage]416 pub type CollectionData<T> = StorageNMap<417 Key = (418 Key<Twox64Concat, CollectionId>,419 Key<Twox64Concat, CollectionField>,420 ),421 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,422 QueryKind = ValueQuery,423 >;424425 #[pallet::storage]426 pub type AdminAmount<T> = StorageMap<427 Hasher = Blake2_128Concat,428 Key = CollectionId,429 Value = u32,430 QueryKind = ValueQuery,431 >;432433 434 #[pallet::storage]435 pub type IsAdmin<T: Config> = StorageNMap<436 Key = (437 Key<Blake2_128Concat, CollectionId>,438 Key<Blake2_128Concat, T::CrossAccountId>,439 ),440 Value = bool,441 QueryKind = ValueQuery,442 >;443444 445 #[pallet::storage]446 pub type Allowlist<T: Config> = StorageNMap<447 Key = (448 Key<Blake2_128Concat, CollectionId>,449 Key<Blake2_128Concat, T::CrossAccountId>,450 ),451 Value = bool,452 QueryKind = ValueQuery,453 >;454455 456 #[pallet::storage]457 pub type DummyStorageValue<T: Config> = StorageValue<458 Value = (459 CollectionStats,460 CollectionId,461 TokenId,462 PhantomType<TokenData<T::CrossAccountId>>,463 PhantomType<RpcCollection<T::AccountId>>,464 ),465 QueryKind = OptionQuery,466 >;467468 #[pallet::hooks]469 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {470 fn on_runtime_upgrade() -> Weight {471 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {472 use up_data_structs::{CollectionVersion1, CollectionVersion2};473 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {474 Self::set_field_raw(475 id,476 CollectionField::OffchainSchema,477 v.offchain_schema.clone().into_inner(),478 )479 .expect("data has lower bounds than field");480 Self::set_field_raw(481 id,482 CollectionField::VariableOnChainSchema,483 v.variable_on_chain_schema.clone().into_inner(),484 )485 .expect("data has lower bounds than field");486 Self::set_field_raw(487 id,488 CollectionField::ConstOnChainSchema,489 v.const_on_chain_schema.clone().into_inner(),490 )491 .expect("data has lower bounds than field");492493 Some(CollectionVersion2::from(v))494 });495 }496497 0498 }499 }500}501502impl<T: Config> Pallet<T> {503 504 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {505 ensure!(506 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,507 <Error<T>>::AddressIsZero508 );509 Ok(())510 }511 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {512 <IsAdmin<T>>::iter_prefix((collection,))513 .map(|(a, _)| a)514 .collect()515 }516 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {517 <Allowlist<T>>::iter_prefix((collection,))518 .map(|(a, _)| a)519 .collect()520 }521 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {522 <Allowlist<T>>::get((collection, user))523 }524 pub fn collection_stats() -> CollectionStats {525 let created = <CreatedCollectionCount<T>>::get();526 let destroyed = <DestroyedCollectionCount<T>>::get();527 CollectionStats {528 created: created.0,529 destroyed: destroyed.0,530 alive: created.0 - destroyed.0,531 }532 }533534 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {535 let collection = <CollectionById<T>>::get(collection);536 if collection.is_none() {537 return None;538 }539540 let collection = collection.unwrap();541 let limits = collection.limits;542 let effective_limits = CollectionLimits {543 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),544 sponsored_data_size: Some(limits.sponsored_data_size()),545 sponsored_data_rate_limit: Some(546 limits547 .sponsored_data_rate_limit548 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),549 ),550 token_limit: Some(limits.token_limit()),551 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(552 match collection.mode {553 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,554 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,555 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,556 },557 )),558 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),559 owner_can_transfer: Some(limits.owner_can_transfer()),560 owner_can_destroy: Some(limits.owner_can_destroy()),561 transfers_enabled: Some(limits.transfers_enabled()),562 nesting_rule: Some(limits.nesting_rule().clone()),563 };564565 Some(effective_limits)566 }567568 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {569 let Collection {570 name,571 description,572 owner,573 mode,574 access,575 token_prefix,576 mint_mode,577 schema_version,578 sponsorship,579 limits,580 meta_update_permission,581 } = <CollectionById<T>>::get(collection)?;582583 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)584 .iter()585 .map(|(key, permission)| PropertyKeyPermission {586 key: key.clone(),587 permission: permission.clone(),588 })589 .collect();590591 let properties = <CollectionProperties<T>>::get(collection)592 .iter()593 .map(|(key, value)| Property {594 key: key.clone(),595 value: value.clone()596 })597 .collect();598599 Some(RpcCollection {600 name: name.into_inner(),601 description: description.into_inner(),602 owner,603 mode,604 access,605 token_prefix: token_prefix.into_inner(),606 mint_mode,607 schema_version,608 sponsorship,609 limits,610 meta_update_permission,611 offchain_schema: <CollectionData<T>>::get((612 collection,613 CollectionField::OffchainSchema,614 ))615 .into_inner(),616 const_on_chain_schema: <CollectionData<T>>::get((617 collection,618 CollectionField::ConstOnChainSchema,619 ))620 .into_inner(),621 variable_on_chain_schema: <CollectionData<T>>::get((622 collection,623 CollectionField::VariableOnChainSchema,624 ))625 .into_inner(),626 token_property_permissions,627 properties,628 })629 }630}631632impl<T: Config> Pallet<T> {633 pub fn init_collection(634 owner: T::AccountId,635 data: CreateCollectionData<T::AccountId>,636 ) -> Result<CollectionId, DispatchError> {637 {638 ensure!(639 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,640 Error::<T>::CollectionTokenPrefixLimitExceeded641 );642 }643644 let created_count = <CreatedCollectionCount<T>>::get()645 .0646 .checked_add(1)647 .ok_or(ArithmeticError::Overflow)?;648 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;649 let id = CollectionId(created_count);650651 652 ensure!(653 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,654 <Error<T>>::TotalCollectionsLimitExceeded655 );656657 658659 let collection = Collection {660 owner: owner.clone(),661 name: data.name,662 mode: data.mode.clone(),663 mint_mode: false,664 access: data.access.unwrap_or_default(),665 description: data.description,666 token_prefix: data.token_prefix,667 schema_version: data.schema_version.unwrap_or_default(),668 sponsorship: data669 .pending_sponsor670 .map(SponsorshipState::Unconfirmed)671 .unwrap_or_default(),672 limits: data673 .limits674 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))675 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,676 meta_update_permission: data.meta_update_permission.unwrap_or_default(),677 };678679 CollectionProperties::<T>::insert(680 id,681 Properties::from_collection_props_vec(data.properties)682 .map_err(|e| -> Error<T> { e.into() })?,683 );684685 let token_props_permissions: PropertiesPermissionMap = data686 .token_property_permissions687 .into_iter()688 .map(|property| (property.key, property.permission))689 .collect::<BTreeMap<_, _>>()690 .try_into()691 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;692693 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);694695 696 {697 let mut imbalance =698 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();699 imbalance.subsume(700 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(701 &T::TreasuryAccountId::get(),702 T::CollectionCreationPrice::get(),703 ),704 );705 <T as Config>::Currency::settle(706 &owner,707 imbalance,708 WithdrawReasons::TRANSFER,709 ExistenceRequirement::KeepAlive,710 )711 .map_err(|_| Error::<T>::NotSufficientFounds)?;712 }713714 <CreatedCollectionCount<T>>::put(created_count);715 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));716 <CollectionById<T>>::insert(id, collection);717 Self::set_field_raw(718 id,719 CollectionField::OffchainSchema,720 data.offchain_schema.into_inner(),721 )722 .expect("data has lower bounds than field");723 Self::set_field_raw(724 id,725 CollectionField::VariableOnChainSchema,726 data.variable_on_chain_schema.into_inner(),727 )728 .expect("data has lower bounds than field");729 Self::set_field_raw(730 id,731 CollectionField::ConstOnChainSchema,732 data.const_on_chain_schema.into_inner(),733 )734 .expect("data has lower bounds than field");735 Ok(id)736 }737738 pub fn destroy_collection(739 collection: CollectionHandle<T>,740 sender: &T::CrossAccountId,741 ) -> DispatchResult {742 ensure!(743 collection.limits.owner_can_destroy(),744 <Error<T>>::NoPermission,745 );746 collection.check_is_owner(sender)?;747748 let destroyed_collections = <DestroyedCollectionCount<T>>::get()749 .0750 .checked_add(1)751 .ok_or(ArithmeticError::Overflow)?;752753 754755 <DestroyedCollectionCount<T>>::put(destroyed_collections);756 <CollectionById<T>>::remove(collection.id);757 <CollectionData<T>>::remove_prefix((collection.id,), None);758 <AdminAmount<T>>::remove(collection.id);759 <IsAdmin<T>>::remove_prefix((collection.id,), None);760 <Allowlist<T>>::remove_prefix((collection.id,), None);761762 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));763 Ok(())764 }765766 pub fn set_collection_property(767 collection: &CollectionHandle<T>,768 sender: &T::CrossAccountId,769 property: Property,770 ) -> DispatchResult {771 collection.check_is_owner_or_admin(sender)?;772773 CollectionProperties::<T>::try_mutate(collection.id, |properties| {774 properties.try_set_property(property.clone())775 })776 .map_err(|e| -> Error<T> { e.into() })?;777778 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));779780 Ok(())781 }782783 pub fn set_collection_properties(784 collection: &CollectionHandle<T>,785 sender: &T::CrossAccountId,786 properties: Vec<Property>,787 ) -> DispatchResult {788 for property in properties {789 Self::set_collection_property(collection, sender, property)?;790 }791792 Ok(())793 }794795 pub fn delete_collection_property(796 collection: &CollectionHandle<T>,797 sender: &T::CrossAccountId,798 property_key: PropertyKey,799 ) -> DispatchResult {800 collection.check_is_owner_or_admin(sender)?;801802 CollectionProperties::<T>::mutate(collection.id, |properties| {803 properties.remove_property(&property_key);804 });805806 Self::deposit_event(Event::CollectionPropertyDeleted(807 collection.id,808 property_key,809 ));810811 Ok(())812 }813814 pub fn delete_collection_properties(815 collection: &CollectionHandle<T>,816 sender: &T::CrossAccountId,817 property_keys: Vec<PropertyKey>,818 ) -> DispatchResult {819 for key in property_keys {820 Self::delete_collection_property(collection, sender, key)?;821 }822823 Ok(())824 }825826 pub fn set_property_permission(827 collection: &CollectionHandle<T>,828 sender: &T::CrossAccountId,829 property_permission: PropertyKeyPermission,830 ) -> DispatchResult {831 collection.check_is_owner_or_admin(sender)?;832833 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);834 let current_permission = all_permissions.get(&property_permission.key);835 if matches![836 current_permission,837 Some(PropertyPermission { mutable: false, .. })838 ] {839 return Err(<Error<T>>::NoPermission.into());840 }841842 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {843 let property_permission = property_permission.clone();844 permissions.try_insert(property_permission.key, property_permission.permission)845 })846 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;847848 Self::deposit_event(Event::PropertyPermissionSet(849 collection.id,850 property_permission,851 ));852853 Ok(())854 }855856 pub fn set_property_permissions(857 collection: &CollectionHandle<T>,858 sender: &T::CrossAccountId,859 property_permissions: Vec<PropertyKeyPermission>,860 ) -> DispatchResult {861 for prop_pemission in property_permissions {862 Self::set_property_permission(collection, sender, prop_pemission)?;863 }864865 Ok(())866 }867868 pub fn bytes_keys_to_property_keys(869 keys: Vec<Vec<u8>>,870 ) -> Result<Vec<PropertyKey>, DispatchError> {871 keys.into_iter()872 .map(|key| -> Result<PropertyKey, DispatchError> {873 key.try_into()874 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())875 })876 .collect::<Result<Vec<PropertyKey>, DispatchError>>()877 }878879 pub fn filter_collection_properties(880 collection_id: CollectionId,881 keys: Vec<PropertyKey>,882 ) -> Result<Vec<Property>, DispatchError> {883 let properties = Self::collection_properties(collection_id);884885 let properties = keys886 .into_iter()887 .filter_map(|key| {888 properties.get_property(&key).map(|value| Property {889 key,890 value: value.clone(),891 })892 })893 .collect();894895 Ok(properties)896 }897898 pub fn filter_property_permissions(899 collection_id: CollectionId,900 keys: Vec<PropertyKey>,901 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {902 let permissions = Self::property_permissions(collection_id);903904 let key_permissions = keys905 .into_iter()906 .filter_map(|key| {907 permissions908 .get(&key)909 .map(|permission| PropertyKeyPermission {910 key,911 permission: permission.clone(),912 })913 })914 .collect();915916 Ok(key_permissions)917 }918919 fn set_field_raw(920 collection_id: CollectionId,921 field: CollectionField,922 value: Vec<u8>,923 ) -> DispatchResult {924 if !value.is_empty() {925 <CollectionData<T>>::insert(926 (collection_id, field),927 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,928 )929 } else {930 <CollectionData<T>>::remove((collection_id, field));931 }932 Ok(())933 }934935 pub fn set_field(936 collection: &CollectionHandle<T>,937 sender: &T::CrossAccountId,938 field: CollectionField,939 value: Vec<u8>,940 ) -> DispatchResult {941 collection.check_is_owner_or_admin(sender)?;942943 944945 Self::set_field_raw(collection.id, field, value)946 }947948 pub fn toggle_allowlist(949 collection: &CollectionHandle<T>,950 sender: &T::CrossAccountId,951 user: &T::CrossAccountId,952 allowed: bool,953 ) -> DispatchResult {954 collection.check_is_owner_or_admin(sender)?;955956 957958 if allowed {959 <Allowlist<T>>::insert((collection.id, user), true);960 } else {961 <Allowlist<T>>::remove((collection.id, user));962 }963964 Ok(())965 }966967 pub fn toggle_admin(968 collection: &CollectionHandle<T>,969 sender: &T::CrossAccountId,970 user: &T::CrossAccountId,971 admin: bool,972 ) -> DispatchResult {973 collection.check_is_owner_or_admin(sender)?;974975 let was_admin = <IsAdmin<T>>::get((collection.id, user));976 if was_admin == admin {977 return Ok(());978 }979 let amount = <AdminAmount<T>>::get(collection.id);980981 if admin {982 let amount = amount983 .checked_add(1)984 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;985 ensure!(986 amount <= Self::collection_admins_limit(),987 <Error<T>>::CollectionAdminCountExceeded,988 );989990 991992 <AdminAmount<T>>::insert(collection.id, amount);993 <IsAdmin<T>>::insert((collection.id, user), true);994 } else {995 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));996 <IsAdmin<T>>::remove((collection.id, user));997 }998999 Ok(())1000 }10011002 pub fn clamp_limits(1003 mode: CollectionMode,1004 old_limit: &CollectionLimits,1005 mut new_limit: CollectionLimits,1006 ) -> Result<CollectionLimits, DispatchError> {1007 macro_rules! limit_default {1008 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1009 $(1010 if let Some($new) = $new.$field {1011 let $old = $old.$field($($arg)?);1012 let _ = $new;1013 let _ = $old;1014 $check1015 } else {1016 $new.$field = $old.$field1017 }1018 )*1019 }};1020 }10211022 limit_default!(old_limit, new_limit,1023 account_token_ownership_limit => ensure!(1024 new_limit <= MAX_TOKEN_OWNERSHIP,1025 <Error<T>>::CollectionLimitBoundsExceeded,1026 ),1027 sponsor_transfer_timeout(match mode {1028 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1029 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1030 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1031 }) => ensure!(1032 new_limit <= MAX_SPONSOR_TIMEOUT,1033 <Error<T>>::CollectionLimitBoundsExceeded,1034 ),1035 sponsored_data_size => ensure!(1036 new_limit <= CUSTOM_DATA_LIMIT,1037 <Error<T>>::CollectionLimitBoundsExceeded,1038 ),1039 token_limit => ensure!(1040 old_limit >= new_limit && new_limit > 0,1041 <Error<T>>::CollectionTokenLimitExceeded1042 ),1043 owner_can_transfer => ensure!(1044 old_limit || !new_limit,1045 <Error<T>>::OwnerPermissionsCantBeReverted,1046 ),1047 owner_can_destroy => ensure!(1048 old_limit || !new_limit,1049 <Error<T>>::OwnerPermissionsCantBeReverted,1050 ),1051 sponsored_data_rate_limit => {},1052 transfers_enabled => {},1053 );1054 Ok(new_limit)1055 }1056}10571058#[macro_export]1059macro_rules! unsupported {1060 () => {1061 Err(<Error<T>>::UnsupportedOperation.into())1062 };1063}106410651066pub trait CommonWeightInfo<CrossAccountId> {1067 fn create_item() -> Weight;1068 fn create_multiple_items(amount: u32) -> Weight;1069 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1070 fn burn_item() -> Weight;1071 fn set_collection_properties(amount: u32) -> Weight;1072 fn delete_collection_properties(amount: u32) -> Weight;1073 fn set_token_properties(amount: u32) -> Weight;1074 fn delete_token_properties(amount: u32) -> Weight;1075 fn set_property_permissions(amount: u32) -> Weight;1076 fn transfer() -> Weight;1077 fn approve() -> Weight;1078 fn transfer_from() -> Weight;1079 fn burn_from() -> Weight;1080 fn set_variable_metadata(bytes: u32) -> Weight;1081}10821083pub trait CommonCollectionOperations<T: Config> {1084 fn create_item(1085 &self,1086 sender: T::CrossAccountId,1087 to: T::CrossAccountId,1088 data: CreateItemData,1089 nesting_budget: &dyn Budget,1090 ) -> DispatchResultWithPostInfo;1091 fn create_multiple_items(1092 &self,1093 sender: T::CrossAccountId,1094 to: T::CrossAccountId,1095 data: Vec<CreateItemData>,1096 nesting_budget: &dyn Budget,1097 ) -> DispatchResultWithPostInfo;1098 fn create_multiple_items_ex(1099 &self,1100 sender: T::CrossAccountId,1101 data: CreateItemExData<T::CrossAccountId>,1102 nesting_budget: &dyn Budget,1103 ) -> DispatchResultWithPostInfo;1104 fn burn_item(1105 &self,1106 sender: T::CrossAccountId,1107 token: TokenId,1108 amount: u128,1109 ) -> DispatchResultWithPostInfo;1110 fn set_collection_properties(1111 &self,1112 sender: T::CrossAccountId,1113 properties: Vec<Property>,1114 ) -> DispatchResultWithPostInfo;1115 fn delete_collection_properties(1116 &self,1117 sender: &T::CrossAccountId,1118 property_keys: Vec<PropertyKey>,1119 ) -> DispatchResultWithPostInfo;1120 fn set_token_properties(1121 &self,1122 sender: T::CrossAccountId,1123 token_id: TokenId,1124 property: Vec<Property>,1125 ) -> DispatchResultWithPostInfo;1126 fn delete_token_properties(1127 &self,1128 sender: T::CrossAccountId,1129 token_id: TokenId,1130 property_keys: Vec<PropertyKey>,1131 ) -> DispatchResultWithPostInfo;1132 fn set_property_permissions(1133 &self,1134 sender: &T::CrossAccountId,1135 property_permissions: Vec<PropertyKeyPermission>,1136 ) -> DispatchResultWithPostInfo;1137 fn transfer(1138 &self,1139 sender: T::CrossAccountId,1140 to: T::CrossAccountId,1141 token: TokenId,1142 amount: u128,1143 nesting_budget: &dyn Budget,1144 ) -> DispatchResultWithPostInfo;1145 fn approve(1146 &self,1147 sender: T::CrossAccountId,1148 spender: T::CrossAccountId,1149 token: TokenId,1150 amount: u128,1151 ) -> DispatchResultWithPostInfo;1152 fn transfer_from(1153 &self,1154 sender: T::CrossAccountId,1155 from: T::CrossAccountId,1156 to: T::CrossAccountId,1157 token: TokenId,1158 amount: u128,1159 nesting_budget: &dyn Budget,1160 ) -> DispatchResultWithPostInfo;1161 fn burn_from(1162 &self,1163 sender: T::CrossAccountId,1164 from: T::CrossAccountId,1165 token: TokenId,1166 amount: u128,1167 nesting_budget: &dyn Budget,1168 ) -> DispatchResultWithPostInfo;11691170 fn set_variable_metadata(1171 &self,1172 sender: T::CrossAccountId,1173 token: TokenId,1174 data: BoundedVec<u8, CustomDataLimit>,1175 ) -> DispatchResultWithPostInfo;11761177 fn check_nesting(1178 &self,1179 sender: T::CrossAccountId,1180 from: (CollectionId, TokenId),1181 under: TokenId,1182 budget: &dyn Budget,1183 ) -> DispatchResult;11841185 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1186 fn collection_tokens(&self) -> Vec<TokenId>;1187 fn token_exists(&self, token: TokenId) -> bool;1188 fn last_token_id(&self) -> TokenId;11891190 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1191 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1192 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1193 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1194 1195 fn total_supply(&self) -> u32;1196 1197 fn account_balance(&self, account: T::CrossAccountId) -> u32;1198 1199 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1200 fn allowance(1201 &self,1202 sender: T::CrossAccountId,1203 spender: T::CrossAccountId,1204 token: TokenId,1205 ) -> u128;1206}120712081209pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1210 let post_info = PostDispatchInfo {1211 actual_weight: Some(weight),1212 pays_fee: Pays::Yes,1213 };1214 match res {1215 Ok(()) => Ok(post_info),1216 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1217 }1218}12191220impl<T: Config> From<PropertiesError> for Error<T> {1221 fn from(error: PropertiesError) -> Self {1222 match error {1223 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1224 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1225 }1226 }1227}