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 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 } = <CollectionById<T>>::get(collection)?;577578 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)579 .iter()580 .map(|(key, permission)| PropertyKeyPermission {581 key: key.clone(),582 permission: permission.clone(),583 })584 .collect();585586 let properties = <CollectionProperties<T>>::get(collection)587 .iter()588 .map(|(key, value)| Property {589 key: key.clone(),590 value: value.clone()591 })592 .collect();593594 Some(RpcCollection {595 name: name.into_inner(),596 description: description.into_inner(),597 owner,598 mode,599 access,600 token_prefix: token_prefix.into_inner(),601 mint_mode,602 schema_version,603 sponsorship,604 limits,605 meta_update_permission,606 offchain_schema: <CollectionData<T>>::get((607 collection,608 CollectionField::OffchainSchema,609 ))610 .into_inner(),611 const_on_chain_schema: <CollectionData<T>>::get((612 collection,613 CollectionField::ConstOnChainSchema,614 ))615 .into_inner(),616 variable_on_chain_schema: <CollectionData<T>>::get((617 collection,618 CollectionField::VariableOnChainSchema,619 ))620 .into_inner(),621 token_property_permissions,622 properties,623 })624 }625}626627impl<T: Config> Pallet<T> {628 pub fn init_collection(629 owner: T::AccountId,630 data: CreateCollectionData<T::AccountId>,631 ) -> Result<CollectionId, DispatchError> {632 {633 ensure!(634 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,635 Error::<T>::CollectionTokenPrefixLimitExceeded636 );637 }638639 let created_count = <CreatedCollectionCount<T>>::get()640 .0641 .checked_add(1)642 .ok_or(ArithmeticError::Overflow)?;643 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;644 let id = CollectionId(created_count);645646 647 ensure!(648 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,649 <Error<T>>::TotalCollectionsLimitExceeded650 );651652 653654 let collection = Collection {655 owner: owner.clone(),656 name: data.name,657 mode: data.mode.clone(),658 mint_mode: false,659 access: data.access.unwrap_or_default(),660 description: data.description,661 token_prefix: data.token_prefix,662 schema_version: data.schema_version.unwrap_or_default(),663 sponsorship: data664 .pending_sponsor665 .map(SponsorshipState::Unconfirmed)666 .unwrap_or_default(),667 limits: data668 .limits669 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))670 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,671 meta_update_permission: data.meta_update_permission.unwrap_or_default(),672 };673674 CollectionProperties::<T>::insert(675 id,676 Properties::from_collection_props_vec(data.properties)677 .map_err(|e| -> Error<T> { e.into() })?,678 );679680 let token_props_permissions: PropertiesPermissionMap = data681 .token_property_permissions682 .into_iter()683 .map(|property| (property.key, property.permission))684 .collect::<BTreeMap<_, _>>()685 .try_into()686 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;687688 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);689690 691 {692 let mut imbalance =693 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();694 imbalance.subsume(695 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(696 &T::TreasuryAccountId::get(),697 T::CollectionCreationPrice::get(),698 ),699 );700 <T as Config>::Currency::settle(701 &owner,702 imbalance,703 WithdrawReasons::TRANSFER,704 ExistenceRequirement::KeepAlive,705 )706 .map_err(|_| Error::<T>::NotSufficientFounds)?;707 }708709 <CreatedCollectionCount<T>>::put(created_count);710 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));711 <CollectionById<T>>::insert(id, collection);712 Self::set_field_raw(713 id,714 CollectionField::OffchainSchema,715 data.offchain_schema.into_inner(),716 )717 .expect("data has lower bounds than field");718 Self::set_field_raw(719 id,720 CollectionField::VariableOnChainSchema,721 data.variable_on_chain_schema.into_inner(),722 )723 .expect("data has lower bounds than field");724 Self::set_field_raw(725 id,726 CollectionField::ConstOnChainSchema,727 data.const_on_chain_schema.into_inner(),728 )729 .expect("data has lower bounds than field");730 Ok(id)731 }732733 pub fn destroy_collection(734 collection: CollectionHandle<T>,735 sender: &T::CrossAccountId,736 ) -> DispatchResult {737 ensure!(738 collection.limits.owner_can_destroy(),739 <Error<T>>::NoPermission,740 );741 collection.check_is_owner(sender)?;742743 let destroyed_collections = <DestroyedCollectionCount<T>>::get()744 .0745 .checked_add(1)746 .ok_or(ArithmeticError::Overflow)?;747748 749750 <DestroyedCollectionCount<T>>::put(destroyed_collections);751 <CollectionById<T>>::remove(collection.id);752 <CollectionData<T>>::remove_prefix((collection.id,), None);753 <AdminAmount<T>>::remove(collection.id);754 <IsAdmin<T>>::remove_prefix((collection.id,), None);755 <Allowlist<T>>::remove_prefix((collection.id,), None);756757 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));758 Ok(())759 }760761 pub fn set_collection_property(762 collection: &CollectionHandle<T>,763 sender: &T::CrossAccountId,764 property: Property,765 ) -> DispatchResult {766 collection.check_is_owner_or_admin(sender)?;767768 CollectionProperties::<T>::try_mutate(collection.id, |properties| {769 properties.try_set_property(property.clone())770 })771 .map_err(|e| -> Error<T> { e.into() })?;772773 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));774775 Ok(())776 }777778 pub fn set_collection_properties(779 collection: &CollectionHandle<T>,780 sender: &T::CrossAccountId,781 properties: Vec<Property>,782 ) -> DispatchResult {783 for property in properties {784 Self::set_collection_property(collection, sender, property)?;785 }786787 Ok(())788 }789790 pub fn delete_collection_property(791 collection: &CollectionHandle<T>,792 sender: &T::CrossAccountId,793 property_key: PropertyKey,794 ) -> DispatchResult {795 collection.check_is_owner_or_admin(sender)?;796797 CollectionProperties::<T>::mutate(collection.id, |properties| {798 properties.remove_property(&property_key);799 });800801 Self::deposit_event(Event::CollectionPropertyDeleted(802 collection.id,803 property_key,804 ));805806 Ok(())807 }808809 pub fn delete_collection_properties(810 collection: &CollectionHandle<T>,811 sender: &T::CrossAccountId,812 property_keys: Vec<PropertyKey>,813 ) -> DispatchResult {814 for key in property_keys {815 Self::delete_collection_property(collection, sender, key)?;816 }817818 Ok(())819 }820821 pub fn set_property_permission(822 collection: &CollectionHandle<T>,823 sender: &T::CrossAccountId,824 property_permission: PropertyKeyPermission,825 ) -> DispatchResult {826 collection.check_is_owner_or_admin(sender)?;827828 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);829 let current_permission = all_permissions.get(&property_permission.key);830 if matches![831 current_permission,832 Some(PropertyPermission { mutable: false, .. })833 ] {834 return Err(<Error<T>>::NoPermission.into());835 }836837 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {838 let property_permission = property_permission.clone();839 permissions.try_insert(property_permission.key, property_permission.permission)840 })841 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;842843 Self::deposit_event(Event::PropertyPermissionSet(844 collection.id,845 property_permission,846 ));847848 Ok(())849 }850851 pub fn set_property_permissions(852 collection: &CollectionHandle<T>,853 sender: &T::CrossAccountId,854 property_permissions: Vec<PropertyKeyPermission>,855 ) -> DispatchResult {856 for prop_pemission in property_permissions {857 Self::set_property_permission(collection, sender, prop_pemission)?;858 }859860 Ok(())861 }862863 pub fn bytes_keys_to_property_keys(864 keys: Vec<Vec<u8>>,865 ) -> Result<Vec<PropertyKey>, DispatchError> {866 keys.into_iter()867 .map(|key| -> Result<PropertyKey, DispatchError> {868 869 key.try_into()870 .map_err(|_| DispatchError::Other("Can't read property key"))871 })872 .collect::<Result<Vec<PropertyKey>, DispatchError>>()873 }874875 pub fn filter_collection_properties(876 collection_id: CollectionId,877 keys: Vec<PropertyKey>,878 ) -> Result<Vec<Property>, DispatchError> {879 let properties = Self::collection_properties(collection_id);880881 let properties = keys882 .into_iter()883 .filter_map(|key| {884 properties.get_property(&key).map(|value| Property {885 key,886 value: value.clone(),887 })888 })889 .collect();890891 Ok(properties)892 }893894 pub fn filter_property_permissions(895 collection_id: CollectionId,896 keys: Vec<PropertyKey>,897 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {898 let permissions = Self::property_permissions(collection_id);899900 let key_permissions = keys901 .into_iter()902 .filter_map(|key| {903 permissions904 .get(&key)905 .map(|permission| PropertyKeyPermission {906 key,907 permission: permission.clone(),908 })909 })910 .collect();911912 Ok(key_permissions)913 }914915 fn set_field_raw(916 collection_id: CollectionId,917 field: CollectionField,918 value: Vec<u8>,919 ) -> DispatchResult {920 if !value.is_empty() {921 <CollectionData<T>>::insert(922 (collection_id, field),923 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,924 )925 } else {926 <CollectionData<T>>::remove((collection_id, field));927 }928 Ok(())929 }930931 pub fn set_field(932 collection: &CollectionHandle<T>,933 sender: &T::CrossAccountId,934 field: CollectionField,935 value: Vec<u8>,936 ) -> DispatchResult {937 collection.check_is_owner_or_admin(sender)?;938939 940941 Self::set_field_raw(collection.id, field, value)942 }943944 pub fn toggle_allowlist(945 collection: &CollectionHandle<T>,946 sender: &T::CrossAccountId,947 user: &T::CrossAccountId,948 allowed: bool,949 ) -> DispatchResult {950 collection.check_is_owner_or_admin(sender)?;951952 953954 if allowed {955 <Allowlist<T>>::insert((collection.id, user), true);956 } else {957 <Allowlist<T>>::remove((collection.id, user));958 }959960 Ok(())961 }962963 pub fn toggle_admin(964 collection: &CollectionHandle<T>,965 sender: &T::CrossAccountId,966 user: &T::CrossAccountId,967 admin: bool,968 ) -> DispatchResult {969 collection.check_is_owner_or_admin(sender)?;970971 let was_admin = <IsAdmin<T>>::get((collection.id, user));972 if was_admin == admin {973 return Ok(());974 }975 let amount = <AdminAmount<T>>::get(collection.id);976977 if admin {978 let amount = amount979 .checked_add(1)980 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;981 ensure!(982 amount <= Self::collection_admins_limit(),983 <Error<T>>::CollectionAdminCountExceeded,984 );985986 987988 <AdminAmount<T>>::insert(collection.id, amount);989 <IsAdmin<T>>::insert((collection.id, user), true);990 } else {991 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));992 <IsAdmin<T>>::remove((collection.id, user));993 }994995 Ok(())996 }997998 pub fn clamp_limits(999 mode: CollectionMode,1000 old_limit: &CollectionLimits,1001 mut new_limit: CollectionLimits,1002 ) -> Result<CollectionLimits, DispatchError> {1003 macro_rules! limit_default {1004 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1005 $(1006 if let Some($new) = $new.$field {1007 let $old = $old.$field($($arg)?);1008 let _ = $new;1009 let _ = $old;1010 $check1011 } else {1012 $new.$field = $old.$field1013 }1014 )*1015 }};1016 }10171018 limit_default!(old_limit, new_limit,1019 account_token_ownership_limit => ensure!(1020 new_limit <= MAX_TOKEN_OWNERSHIP,1021 <Error<T>>::CollectionLimitBoundsExceeded,1022 ),1023 sponsor_transfer_timeout(match mode {1024 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1025 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1026 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1027 }) => ensure!(1028 new_limit <= MAX_SPONSOR_TIMEOUT,1029 <Error<T>>::CollectionLimitBoundsExceeded,1030 ),1031 sponsored_data_size => ensure!(1032 new_limit <= CUSTOM_DATA_LIMIT,1033 <Error<T>>::CollectionLimitBoundsExceeded,1034 ),1035 token_limit => ensure!(1036 old_limit >= new_limit && new_limit > 0,1037 <Error<T>>::CollectionTokenLimitExceeded1038 ),1039 owner_can_transfer => ensure!(1040 old_limit || !new_limit,1041 <Error<T>>::OwnerPermissionsCantBeReverted,1042 ),1043 owner_can_destroy => ensure!(1044 old_limit || !new_limit,1045 <Error<T>>::OwnerPermissionsCantBeReverted,1046 ),1047 sponsored_data_rate_limit => {},1048 transfers_enabled => {},1049 );1050 Ok(new_limit)1051 }1052}10531054#[macro_export]1055macro_rules! unsupported {1056 () => {1057 Err(<Error<T>>::UnsupportedOperation.into())1058 };1059}106010611062pub trait CommonWeightInfo<CrossAccountId> {1063 fn create_item() -> Weight;1064 fn create_multiple_items(amount: u32) -> Weight;1065 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1066 fn burn_item() -> Weight;1067 fn set_collection_properties(amount: u32) -> Weight;1068 fn delete_collection_properties(amount: u32) -> Weight;1069 fn set_token_properties(amount: u32) -> Weight;1070 fn delete_token_properties(amount: u32) -> Weight;1071 fn set_property_permissions(amount: u32) -> Weight;1072 fn transfer() -> Weight;1073 fn approve() -> Weight;1074 fn transfer_from() -> Weight;1075 fn burn_from() -> Weight;1076 fn set_variable_metadata(bytes: u32) -> Weight;1077}10781079pub trait CommonCollectionOperations<T: Config> {1080 fn create_item(1081 &self,1082 sender: T::CrossAccountId,1083 to: T::CrossAccountId,1084 data: CreateItemData,1085 nesting_budget: &dyn Budget,1086 ) -> DispatchResultWithPostInfo;1087 fn create_multiple_items(1088 &self,1089 sender: T::CrossAccountId,1090 to: T::CrossAccountId,1091 data: Vec<CreateItemData>,1092 nesting_budget: &dyn Budget,1093 ) -> DispatchResultWithPostInfo;1094 fn create_multiple_items_ex(1095 &self,1096 sender: T::CrossAccountId,1097 data: CreateItemExData<T::CrossAccountId>,1098 nesting_budget: &dyn Budget,1099 ) -> DispatchResultWithPostInfo;1100 fn burn_item(1101 &self,1102 sender: T::CrossAccountId,1103 token: TokenId,1104 amount: u128,1105 ) -> DispatchResultWithPostInfo;1106 fn set_collection_properties(1107 &self,1108 sender: T::CrossAccountId,1109 properties: Vec<Property>,1110 ) -> DispatchResultWithPostInfo;1111 fn delete_collection_properties(1112 &self,1113 sender: &T::CrossAccountId,1114 property_keys: Vec<PropertyKey>,1115 ) -> DispatchResultWithPostInfo;1116 fn set_token_properties(1117 &self,1118 sender: T::CrossAccountId,1119 token_id: TokenId,1120 property: Vec<Property>,1121 ) -> DispatchResultWithPostInfo;1122 fn delete_token_properties(1123 &self,1124 sender: T::CrossAccountId,1125 token_id: TokenId,1126 property_keys: Vec<PropertyKey>,1127 ) -> DispatchResultWithPostInfo;1128 fn set_property_permissions(1129 &self,1130 sender: &T::CrossAccountId,1131 property_permissions: Vec<PropertyKeyPermission>,1132 ) -> DispatchResultWithPostInfo;1133 fn transfer(1134 &self,1135 sender: T::CrossAccountId,1136 to: T::CrossAccountId,1137 token: TokenId,1138 amount: u128,1139 nesting_budget: &dyn Budget,1140 ) -> DispatchResultWithPostInfo;1141 fn approve(1142 &self,1143 sender: T::CrossAccountId,1144 spender: T::CrossAccountId,1145 token: TokenId,1146 amount: u128,1147 ) -> DispatchResultWithPostInfo;1148 fn transfer_from(1149 &self,1150 sender: T::CrossAccountId,1151 from: T::CrossAccountId,1152 to: T::CrossAccountId,1153 token: TokenId,1154 amount: u128,1155 nesting_budget: &dyn Budget,1156 ) -> DispatchResultWithPostInfo;1157 fn burn_from(1158 &self,1159 sender: T::CrossAccountId,1160 from: T::CrossAccountId,1161 token: TokenId,1162 amount: u128,1163 nesting_budget: &dyn Budget,1164 ) -> DispatchResultWithPostInfo;11651166 fn set_variable_metadata(1167 &self,1168 sender: T::CrossAccountId,1169 token: TokenId,1170 data: BoundedVec<u8, CustomDataLimit>,1171 ) -> DispatchResultWithPostInfo;11721173 fn check_nesting(1174 &self,1175 sender: T::CrossAccountId,1176 from: (CollectionId, TokenId),1177 under: TokenId,1178 budget: &dyn Budget,1179 ) -> DispatchResult;11801181 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1182 fn collection_tokens(&self) -> Vec<TokenId>;1183 fn token_exists(&self, token: TokenId) -> bool;1184 fn last_token_id(&self) -> TokenId;11851186 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1187 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1188 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1189 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1190 1191 fn total_supply(&self) -> u32;1192 1193 fn account_balance(&self, account: T::CrossAccountId) -> u32;1194 1195 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1196 fn allowance(1197 &self,1198 sender: T::CrossAccountId,1199 spender: T::CrossAccountId,1200 token: TokenId,1201 ) -> u128;1202}120312041205pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1206 let post_info = PostDispatchInfo {1207 actual_weight: Some(weight),1208 pays_fee: Pays::Yes,1209 };1210 match res {1211 Ok(()) => Ok(post_info),1212 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1213 }1214}12151216impl<T: Config> From<PropertiesError> for Error<T> {1217 fn from(error: PropertiesError) -> Self {1218 match error {1219 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1220 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1221 }1222 }1223}