1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission, TokenData, TrySet,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(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,377378 379 InvalidCharacterInPropertyKey,380 }381382 #[pallet::storage]383 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;384 #[pallet::storage]385 pub type DestroyedCollectionCount<T> =386 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;387388 389 #[pallet::storage]390 pub type CollectionById<T> = StorageMap<391 Hasher = Blake2_128Concat,392 Key = CollectionId,393 Value = Collection<<T as frame_system::Config>::AccountId>,394 QueryKind = OptionQuery,395 >;396397 398 #[pallet::storage]399 #[pallet::getter(fn collection_properties)]400 pub type CollectionProperties<T> = StorageMap<401 Hasher = Blake2_128Concat,402 Key = CollectionId,403 Value = Properties,404 QueryKind = ValueQuery,405 OnEmpty = up_data_structs::CollectionProperties,406 >;407408 #[pallet::storage]409 #[pallet::getter(fn property_permissions)]410 pub type CollectionPropertyPermissions<T> = StorageMap<411 Hasher = Blake2_128Concat,412 Key = CollectionId,413 Value = PropertiesPermissionMap,414 QueryKind = ValueQuery,415 >;416417 418 #[pallet::storage]419 pub type CollectionData<T> = StorageNMap<420 Key = (421 Key<Twox64Concat, CollectionId>,422 Key<Twox64Concat, CollectionField>,423 ),424 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,425 QueryKind = ValueQuery,426 >;427428 #[pallet::storage]429 pub type AdminAmount<T> = StorageMap<430 Hasher = Blake2_128Concat,431 Key = CollectionId,432 Value = u32,433 QueryKind = ValueQuery,434 >;435436 437 #[pallet::storage]438 pub type IsAdmin<T: Config> = StorageNMap<439 Key = (440 Key<Blake2_128Concat, CollectionId>,441 Key<Blake2_128Concat, T::CrossAccountId>,442 ),443 Value = bool,444 QueryKind = ValueQuery,445 >;446447 448 #[pallet::storage]449 pub type Allowlist<T: Config> = StorageNMap<450 Key = (451 Key<Blake2_128Concat, CollectionId>,452 Key<Blake2_128Concat, T::CrossAccountId>,453 ),454 Value = bool,455 QueryKind = ValueQuery,456 >;457458 459 #[pallet::storage]460 pub type DummyStorageValue<T: Config> = StorageValue<461 Value = (462 CollectionStats,463 CollectionId,464 TokenId,465 PhantomType<TokenData<T::CrossAccountId>>,466 PhantomType<RpcCollection<T::AccountId>>,467 ),468 QueryKind = OptionQuery,469 >;470471 #[pallet::hooks]472 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {473 fn on_runtime_upgrade() -> Weight {474 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {475 use up_data_structs::{CollectionVersion1, CollectionVersion2};476 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {477 Self::set_field_raw(478 id,479 CollectionField::OffchainSchema,480 v.offchain_schema.clone().into_inner(),481 )482 .expect("data has lower bounds than field");483 Self::set_field_raw(484 id,485 CollectionField::VariableOnChainSchema,486 v.variable_on_chain_schema.clone().into_inner(),487 )488 .expect("data has lower bounds than field");489 Self::set_field_raw(490 id,491 CollectionField::ConstOnChainSchema,492 v.const_on_chain_schema.clone().into_inner(),493 )494 .expect("data has lower bounds than field");495496 Some(CollectionVersion2::from(v))497 });498 }499500 0501 }502 }503}504505impl<T: Config> Pallet<T> {506 507 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {508 ensure!(509 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,510 <Error<T>>::AddressIsZero511 );512 Ok(())513 }514 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {515 <IsAdmin<T>>::iter_prefix((collection,))516 .map(|(a, _)| a)517 .collect()518 }519 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {520 <Allowlist<T>>::iter_prefix((collection,))521 .map(|(a, _)| a)522 .collect()523 }524 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {525 <Allowlist<T>>::get((collection, user))526 }527 pub fn collection_stats() -> CollectionStats {528 let created = <CreatedCollectionCount<T>>::get();529 let destroyed = <DestroyedCollectionCount<T>>::get();530 CollectionStats {531 created: created.0,532 destroyed: destroyed.0,533 alive: created.0 - destroyed.0,534 }535 }536537 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {538 let collection = <CollectionById<T>>::get(collection);539 if collection.is_none() {540 return None;541 }542543 let collection = collection.unwrap();544 let limits = collection.limits;545 let effective_limits = CollectionLimits {546 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),547 sponsored_data_size: Some(limits.sponsored_data_size()),548 sponsored_data_rate_limit: Some(549 limits550 .sponsored_data_rate_limit551 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),552 ),553 token_limit: Some(limits.token_limit()),554 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(555 match collection.mode {556 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,557 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,558 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,559 },560 )),561 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),562 owner_can_transfer: Some(limits.owner_can_transfer()),563 owner_can_destroy: Some(limits.owner_can_destroy()),564 transfers_enabled: Some(limits.transfers_enabled()),565 nesting_rule: Some(limits.nesting_rule().clone()),566 };567568 Some(effective_limits)569 }570571 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {572 let Collection {573 name,574 description,575 owner,576 mode,577 access,578 token_prefix,579 mint_mode,580 schema_version,581 sponsorship,582 limits,583 meta_update_permission,584 } = <CollectionById<T>>::get(collection)?;585586 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)587 .iter()588 .map(|(key, permission)| PropertyKeyPermission {589 key: key.clone(),590 permission: permission.clone(),591 })592 .collect();593594 let properties = <CollectionProperties<T>>::get(collection)595 .iter()596 .map(|(key, value)| Property {597 key: key.clone(),598 value: value.clone(),599 })600 .collect();601602 Some(RpcCollection {603 name: name.into_inner(),604 description: description.into_inner(),605 owner,606 mode,607 access,608 token_prefix: token_prefix.into_inner(),609 mint_mode,610 schema_version,611 sponsorship,612 limits,613 meta_update_permission,614 offchain_schema: <CollectionData<T>>::get((615 collection,616 CollectionField::OffchainSchema,617 ))618 .into_inner(),619 const_on_chain_schema: <CollectionData<T>>::get((620 collection,621 CollectionField::ConstOnChainSchema,622 ))623 .into_inner(),624 variable_on_chain_schema: <CollectionData<T>>::get((625 collection,626 CollectionField::VariableOnChainSchema,627 ))628 .into_inner(),629 token_property_permissions,630 properties,631 })632 }633}634635impl<T: Config> Pallet<T> {636 pub fn init_collection(637 owner: T::AccountId,638 data: CreateCollectionData<T::AccountId>,639 ) -> Result<CollectionId, DispatchError> {640 {641 ensure!(642 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,643 Error::<T>::CollectionTokenPrefixLimitExceeded644 );645 }646647 let created_count = <CreatedCollectionCount<T>>::get()648 .0649 .checked_add(1)650 .ok_or(ArithmeticError::Overflow)?;651 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;652 let id = CollectionId(created_count);653654 655 ensure!(656 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,657 <Error<T>>::TotalCollectionsLimitExceeded658 );659660 661662 let collection = Collection {663 owner: owner.clone(),664 name: data.name,665 mode: data.mode.clone(),666 mint_mode: false,667 access: data.access.unwrap_or_default(),668 description: data.description,669 token_prefix: data.token_prefix,670 schema_version: data.schema_version.unwrap_or_default(),671 sponsorship: data672 .pending_sponsor673 .map(SponsorshipState::Unconfirmed)674 .unwrap_or_default(),675 limits: data676 .limits677 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))678 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,679 meta_update_permission: data.meta_update_permission.unwrap_or_default(),680 };681682 let mut collection_properties = up_data_structs::CollectionProperties::get();683 collection_properties684 .try_set_from_iter(data.properties.into_iter().map(|p| (p.key, p.value)))685 .map_err(|e| -> Error<T> { e.into() })?;686687 CollectionProperties::<T>::insert(id, collection_properties);688689 let mut token_props_permissions = PropertiesPermissionMap::new();690 token_props_permissions691 .try_set_from_iter(692 data.token_property_permissions693 .into_iter()694 .map(|property| (property.key, property.permission)),695 )696 .map_err(|e| -> Error<T> { e.into() })?;697698 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);699700 701 {702 let mut imbalance =703 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();704 imbalance.subsume(705 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(706 &T::TreasuryAccountId::get(),707 T::CollectionCreationPrice::get(),708 ),709 );710 <T as Config>::Currency::settle(711 &owner,712 imbalance,713 WithdrawReasons::TRANSFER,714 ExistenceRequirement::KeepAlive,715 )716 .map_err(|_| Error::<T>::NotSufficientFounds)?;717 }718719 <CreatedCollectionCount<T>>::put(created_count);720 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));721 <CollectionById<T>>::insert(id, collection);722 Self::set_field_raw(723 id,724 CollectionField::OffchainSchema,725 data.offchain_schema.into_inner(),726 )727 .expect("data has lower bounds than field");728 Self::set_field_raw(729 id,730 CollectionField::VariableOnChainSchema,731 data.variable_on_chain_schema.into_inner(),732 )733 .expect("data has lower bounds than field");734 Self::set_field_raw(735 id,736 CollectionField::ConstOnChainSchema,737 data.const_on_chain_schema.into_inner(),738 )739 .expect("data has lower bounds than field");740 Ok(id)741 }742743 pub fn destroy_collection(744 collection: CollectionHandle<T>,745 sender: &T::CrossAccountId,746 ) -> DispatchResult {747 ensure!(748 collection.limits.owner_can_destroy(),749 <Error<T>>::NoPermission,750 );751 collection.check_is_owner(sender)?;752753 let destroyed_collections = <DestroyedCollectionCount<T>>::get()754 .0755 .checked_add(1)756 .ok_or(ArithmeticError::Overflow)?;757758 759760 <DestroyedCollectionCount<T>>::put(destroyed_collections);761 <CollectionById<T>>::remove(collection.id);762 <CollectionData<T>>::remove_prefix((collection.id,), None);763 <AdminAmount<T>>::remove(collection.id);764 <IsAdmin<T>>::remove_prefix((collection.id,), None);765 <Allowlist<T>>::remove_prefix((collection.id,), None);766767 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));768 Ok(())769 }770771 pub fn set_collection_property(772 collection: &CollectionHandle<T>,773 sender: &T::CrossAccountId,774 property: Property,775 ) -> DispatchResult {776 collection.check_is_owner_or_admin(sender)?;777778 CollectionProperties::<T>::try_mutate(collection.id, |properties| {779 let property = property.clone();780 properties.try_set(property.key, property.value)781 })782 .map_err(|e| -> Error<T> { e.into() })?;783784 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));785786 Ok(())787 }788789 pub fn set_collection_properties(790 collection: &CollectionHandle<T>,791 sender: &T::CrossAccountId,792 properties: Vec<Property>,793 ) -> DispatchResult {794 for property in properties {795 Self::set_collection_property(collection, sender, property)?;796 }797798 Ok(())799 }800801 pub fn delete_collection_property(802 collection: &CollectionHandle<T>,803 sender: &T::CrossAccountId,804 property_key: PropertyKey,805 ) -> DispatchResult {806 collection.check_is_owner_or_admin(sender)?;807808 CollectionProperties::<T>::try_mutate(collection.id, |properties| {809 properties.remove(&property_key)810 })811 .map_err(|e| -> Error<T> { e.into() })?;812813 Self::deposit_event(Event::CollectionPropertyDeleted(814 collection.id,815 property_key,816 ));817818 Ok(())819 }820821 pub fn delete_collection_properties(822 collection: &CollectionHandle<T>,823 sender: &T::CrossAccountId,824 property_keys: Vec<PropertyKey>,825 ) -> DispatchResult {826 for key in property_keys {827 Self::delete_collection_property(collection, sender, key)?;828 }829830 Ok(())831 }832833 pub fn set_property_permission(834 collection: &CollectionHandle<T>,835 sender: &T::CrossAccountId,836 property_permission: PropertyKeyPermission,837 ) -> DispatchResult {838 collection.check_is_owner_or_admin(sender)?;839840 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);841 let current_permission = all_permissions.get(&property_permission.key);842 if matches![843 current_permission,844 Some(PropertyPermission { mutable: false, .. })845 ] {846 return Err(<Error<T>>::NoPermission.into());847 }848849 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {850 let property_permission = property_permission.clone();851 permissions.try_set(property_permission.key, property_permission.permission)852 })853 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;854855 Self::deposit_event(Event::PropertyPermissionSet(856 collection.id,857 property_permission,858 ));859860 Ok(())861 }862863 pub fn set_property_permissions(864 collection: &CollectionHandle<T>,865 sender: &T::CrossAccountId,866 property_permissions: Vec<PropertyKeyPermission>,867 ) -> DispatchResult {868 for prop_pemission in property_permissions {869 Self::set_property_permission(collection, sender, prop_pemission)?;870 }871872 Ok(())873 }874875 pub fn bytes_keys_to_property_keys(876 keys: Vec<Vec<u8>>,877 ) -> Result<Vec<PropertyKey>, DispatchError> {878 keys.into_iter()879 .map(|key| -> Result<PropertyKey, DispatchError> {880 key.try_into()881 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())882 })883 .collect::<Result<Vec<PropertyKey>, DispatchError>>()884 }885886 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {887 let key_str = sp_std::str::from_utf8(key.as_slice())888 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;889890 for ch in key_str.chars() {891 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {892 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());893 }894 }895896 Ok(())897 }898899 pub fn filter_collection_properties(900 collection_id: CollectionId,901 keys: Vec<PropertyKey>,902 ) -> Result<Vec<Property>, DispatchError> {903 let properties = Self::collection_properties(collection_id);904905 let properties = keys906 .into_iter()907 .filter_map(|key| {908 properties.get(&key).map(|value| Property {909 key,910 value: value.clone(),911 })912 })913 .collect();914915 Ok(properties)916 }917918 pub fn filter_property_permissions(919 collection_id: CollectionId,920 keys: Vec<PropertyKey>,921 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {922 let permissions = Self::property_permissions(collection_id);923924 let key_permissions = keys925 .into_iter()926 .filter_map(|key| {927 permissions928 .get(&key)929 .map(|permission| PropertyKeyPermission {930 key,931 permission: permission.clone(),932 })933 })934 .collect();935936 Ok(key_permissions)937 }938939 fn set_field_raw(940 collection_id: CollectionId,941 field: CollectionField,942 value: Vec<u8>,943 ) -> DispatchResult {944 if !value.is_empty() {945 <CollectionData<T>>::insert(946 (collection_id, field),947 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,948 )949 } else {950 <CollectionData<T>>::remove((collection_id, field));951 }952 Ok(())953 }954955 pub fn set_field(956 collection: &CollectionHandle<T>,957 sender: &T::CrossAccountId,958 field: CollectionField,959 value: Vec<u8>,960 ) -> DispatchResult {961 collection.check_is_owner_or_admin(sender)?;962963 964965 Self::set_field_raw(collection.id, field, value)966 }967968 pub fn toggle_allowlist(969 collection: &CollectionHandle<T>,970 sender: &T::CrossAccountId,971 user: &T::CrossAccountId,972 allowed: bool,973 ) -> DispatchResult {974 collection.check_is_owner_or_admin(sender)?;975976 977978 if allowed {979 <Allowlist<T>>::insert((collection.id, user), true);980 } else {981 <Allowlist<T>>::remove((collection.id, user));982 }983984 Ok(())985 }986987 pub fn toggle_admin(988 collection: &CollectionHandle<T>,989 sender: &T::CrossAccountId,990 user: &T::CrossAccountId,991 admin: bool,992 ) -> DispatchResult {993 collection.check_is_owner_or_admin(sender)?;994995 let was_admin = <IsAdmin<T>>::get((collection.id, user));996 if was_admin == admin {997 return Ok(());998 }999 let amount = <AdminAmount<T>>::get(collection.id);10001001 if admin {1002 let amount = amount1003 .checked_add(1)1004 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1005 ensure!(1006 amount <= Self::collection_admins_limit(),1007 <Error<T>>::CollectionAdminCountExceeded,1008 );10091010 10111012 <AdminAmount<T>>::insert(collection.id, amount);1013 <IsAdmin<T>>::insert((collection.id, user), true);1014 } else {1015 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1016 <IsAdmin<T>>::remove((collection.id, user));1017 }10181019 Ok(())1020 }10211022 pub fn clamp_limits(1023 mode: CollectionMode,1024 old_limit: &CollectionLimits,1025 mut new_limit: CollectionLimits,1026 ) -> Result<CollectionLimits, DispatchError> {1027 macro_rules! limit_default {1028 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1029 $(1030 if let Some($new) = $new.$field {1031 let $old = $old.$field($($arg)?);1032 let _ = $new;1033 let _ = $old;1034 $check1035 } else {1036 $new.$field = $old.$field1037 }1038 )*1039 }};1040 }10411042 limit_default!(old_limit, new_limit,1043 account_token_ownership_limit => ensure!(1044 new_limit <= MAX_TOKEN_OWNERSHIP,1045 <Error<T>>::CollectionLimitBoundsExceeded,1046 ),1047 sponsor_transfer_timeout(match mode {1048 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1049 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1050 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1051 }) => ensure!(1052 new_limit <= MAX_SPONSOR_TIMEOUT,1053 <Error<T>>::CollectionLimitBoundsExceeded,1054 ),1055 sponsored_data_size => ensure!(1056 new_limit <= CUSTOM_DATA_LIMIT,1057 <Error<T>>::CollectionLimitBoundsExceeded,1058 ),1059 token_limit => ensure!(1060 old_limit >= new_limit && new_limit > 0,1061 <Error<T>>::CollectionTokenLimitExceeded1062 ),1063 owner_can_transfer => ensure!(1064 old_limit || !new_limit,1065 <Error<T>>::OwnerPermissionsCantBeReverted,1066 ),1067 owner_can_destroy => ensure!(1068 old_limit || !new_limit,1069 <Error<T>>::OwnerPermissionsCantBeReverted,1070 ),1071 sponsored_data_rate_limit => {},1072 transfers_enabled => {},1073 );1074 Ok(new_limit)1075 }1076}10771078#[macro_export]1079macro_rules! unsupported {1080 () => {1081 Err(<Error<T>>::UnsupportedOperation.into())1082 };1083}108410851086pub trait CommonWeightInfo<CrossAccountId> {1087 fn create_item() -> Weight;1088 fn create_multiple_items(amount: u32) -> Weight;1089 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1090 fn burn_item() -> Weight;1091 fn set_collection_properties(amount: u32) -> Weight;1092 fn delete_collection_properties(amount: u32) -> Weight;1093 fn set_token_properties(amount: u32) -> Weight;1094 fn delete_token_properties(amount: u32) -> Weight;1095 fn set_property_permissions(amount: u32) -> Weight;1096 fn transfer() -> Weight;1097 fn approve() -> Weight;1098 fn transfer_from() -> Weight;1099 fn burn_from() -> Weight;1100 fn set_variable_metadata(bytes: u32) -> Weight;1101}11021103pub trait CommonCollectionOperations<T: Config> {1104 fn create_item(1105 &self,1106 sender: T::CrossAccountId,1107 to: T::CrossAccountId,1108 data: CreateItemData,1109 nesting_budget: &dyn Budget,1110 ) -> DispatchResultWithPostInfo;1111 fn create_multiple_items(1112 &self,1113 sender: T::CrossAccountId,1114 to: T::CrossAccountId,1115 data: Vec<CreateItemData>,1116 nesting_budget: &dyn Budget,1117 ) -> DispatchResultWithPostInfo;1118 fn create_multiple_items_ex(1119 &self,1120 sender: T::CrossAccountId,1121 data: CreateItemExData<T::CrossAccountId>,1122 nesting_budget: &dyn Budget,1123 ) -> DispatchResultWithPostInfo;1124 fn burn_item(1125 &self,1126 sender: T::CrossAccountId,1127 token: TokenId,1128 amount: u128,1129 ) -> DispatchResultWithPostInfo;1130 fn set_collection_properties(1131 &self,1132 sender: T::CrossAccountId,1133 properties: Vec<Property>,1134 ) -> DispatchResultWithPostInfo;1135 fn delete_collection_properties(1136 &self,1137 sender: &T::CrossAccountId,1138 property_keys: Vec<PropertyKey>,1139 ) -> DispatchResultWithPostInfo;1140 fn set_token_properties(1141 &self,1142 sender: T::CrossAccountId,1143 token_id: TokenId,1144 property: Vec<Property>,1145 ) -> DispatchResultWithPostInfo;1146 fn delete_token_properties(1147 &self,1148 sender: T::CrossAccountId,1149 token_id: TokenId,1150 property_keys: Vec<PropertyKey>,1151 ) -> DispatchResultWithPostInfo;1152 fn set_property_permissions(1153 &self,1154 sender: &T::CrossAccountId,1155 property_permissions: Vec<PropertyKeyPermission>,1156 ) -> DispatchResultWithPostInfo;1157 fn transfer(1158 &self,1159 sender: T::CrossAccountId,1160 to: T::CrossAccountId,1161 token: TokenId,1162 amount: u128,1163 nesting_budget: &dyn Budget,1164 ) -> DispatchResultWithPostInfo;1165 fn approve(1166 &self,1167 sender: T::CrossAccountId,1168 spender: T::CrossAccountId,1169 token: TokenId,1170 amount: u128,1171 ) -> DispatchResultWithPostInfo;1172 fn transfer_from(1173 &self,1174 sender: T::CrossAccountId,1175 from: T::CrossAccountId,1176 to: T::CrossAccountId,1177 token: TokenId,1178 amount: u128,1179 nesting_budget: &dyn Budget,1180 ) -> DispatchResultWithPostInfo;1181 fn burn_from(1182 &self,1183 sender: T::CrossAccountId,1184 from: T::CrossAccountId,1185 token: TokenId,1186 amount: u128,1187 nesting_budget: &dyn Budget,1188 ) -> DispatchResultWithPostInfo;11891190 fn set_variable_metadata(1191 &self,1192 sender: T::CrossAccountId,1193 token: TokenId,1194 data: BoundedVec<u8, CustomDataLimit>,1195 ) -> DispatchResultWithPostInfo;11961197 fn check_nesting(1198 &self,1199 sender: T::CrossAccountId,1200 from: (CollectionId, TokenId),1201 under: TokenId,1202 budget: &dyn Budget,1203 ) -> DispatchResult;12041205 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1206 fn collection_tokens(&self) -> Vec<TokenId>;1207 fn token_exists(&self, token: TokenId) -> bool;1208 fn last_token_id(&self) -> TokenId;12091210 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1211 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1212 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1213 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1214 1215 fn total_supply(&self) -> u32;1216 1217 fn account_balance(&self, account: T::CrossAccountId) -> u32;1218 1219 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1220 fn allowance(1221 &self,1222 sender: T::CrossAccountId,1223 spender: T::CrossAccountId,1224 token: TokenId,1225 ) -> u128;1226}122712281229pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1230 let post_info = PostDispatchInfo {1231 actual_weight: Some(weight),1232 pays_fee: Pays::Yes,1233 };1234 match res {1235 Ok(()) => Ok(post_info),1236 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1237 }1238}12391240impl<T: Config> From<PropertiesError> for Error<T> {1241 fn from(error: PropertiesError) -> Self {1242 match error {1243 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1244 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1245 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1246 }1247 }1248}