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_properties.try_set_from_iter(684 data.properties.into_iter()685 .map(|p| (p.key, p.value))686 ).map_err(|e| -> Error<T> { e.into() })?;687688 CollectionProperties::<T>::insert(id, collection_properties);689690 let mut token_props_permissions = PropertiesPermissionMap::new();691 token_props_permissions.try_set_from_iter(692 data.token_property_permissions693 .into_iter()694 .map(|property| (property.key, property.permission))695 ).map_err(|e| -> Error<T> { e.into() })?;696697 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);698699 700 {701 let mut imbalance =702 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();703 imbalance.subsume(704 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(705 &T::TreasuryAccountId::get(),706 T::CollectionCreationPrice::get(),707 ),708 );709 <T as Config>::Currency::settle(710 &owner,711 imbalance,712 WithdrawReasons::TRANSFER,713 ExistenceRequirement::KeepAlive,714 )715 .map_err(|_| Error::<T>::NotSufficientFounds)?;716 }717718 <CreatedCollectionCount<T>>::put(created_count);719 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));720 <CollectionById<T>>::insert(id, collection);721 Self::set_field_raw(722 id,723 CollectionField::OffchainSchema,724 data.offchain_schema.into_inner(),725 )726 .expect("data has lower bounds than field");727 Self::set_field_raw(728 id,729 CollectionField::VariableOnChainSchema,730 data.variable_on_chain_schema.into_inner(),731 )732 .expect("data has lower bounds than field");733 Self::set_field_raw(734 id,735 CollectionField::ConstOnChainSchema,736 data.const_on_chain_schema.into_inner(),737 )738 .expect("data has lower bounds than field");739 Ok(id)740 }741742 pub fn destroy_collection(743 collection: CollectionHandle<T>,744 sender: &T::CrossAccountId,745 ) -> DispatchResult {746 ensure!(747 collection.limits.owner_can_destroy(),748 <Error<T>>::NoPermission,749 );750 collection.check_is_owner(sender)?;751752 let destroyed_collections = <DestroyedCollectionCount<T>>::get()753 .0754 .checked_add(1)755 .ok_or(ArithmeticError::Overflow)?;756757 758759 <DestroyedCollectionCount<T>>::put(destroyed_collections);760 <CollectionById<T>>::remove(collection.id);761 <CollectionData<T>>::remove_prefix((collection.id,), None);762 <AdminAmount<T>>::remove(collection.id);763 <IsAdmin<T>>::remove_prefix((collection.id,), None);764 <Allowlist<T>>::remove_prefix((collection.id,), None);765766 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));767 Ok(())768 }769770 pub fn set_collection_property(771 collection: &CollectionHandle<T>,772 sender: &T::CrossAccountId,773 property: Property,774 ) -> DispatchResult {775 collection.check_is_owner_or_admin(sender)?;776777 CollectionProperties::<T>::try_mutate(collection.id, |properties| {778 let property = property.clone();779 properties.try_set(property.key, property.value)780 })781 .map_err(|e| -> Error<T> { e.into() })?;782783 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));784785 Ok(())786 }787788 pub fn set_collection_properties(789 collection: &CollectionHandle<T>,790 sender: &T::CrossAccountId,791 properties: Vec<Property>,792 ) -> DispatchResult {793 for property in properties {794 Self::set_collection_property(collection, sender, property)?;795 }796797 Ok(())798 }799800 pub fn delete_collection_property(801 collection: &CollectionHandle<T>,802 sender: &T::CrossAccountId,803 property_key: PropertyKey,804 ) -> DispatchResult {805 collection.check_is_owner_or_admin(sender)?;806807 CollectionProperties::<T>::try_mutate(collection.id, |properties| {808 properties.remove(&property_key)809 }).map_err(|e| -> Error<T> { e.into() })?;810811 Self::deposit_event(Event::CollectionPropertyDeleted(812 collection.id,813 property_key,814 ));815816 Ok(())817 }818819 pub fn delete_collection_properties(820 collection: &CollectionHandle<T>,821 sender: &T::CrossAccountId,822 property_keys: Vec<PropertyKey>,823 ) -> DispatchResult {824 for key in property_keys {825 Self::delete_collection_property(collection, sender, key)?;826 }827828 Ok(())829 }830831 pub fn set_property_permission(832 collection: &CollectionHandle<T>,833 sender: &T::CrossAccountId,834 property_permission: PropertyKeyPermission,835 ) -> DispatchResult {836 collection.check_is_owner_or_admin(sender)?;837838 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);839 let current_permission = all_permissions.get(&property_permission.key);840 if matches![841 current_permission,842 Some(PropertyPermission { mutable: false, .. })843 ] {844 return Err(<Error<T>>::NoPermission.into());845 }846847 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {848 let property_permission = property_permission.clone();849 permissions.try_set(property_permission.key, property_permission.permission)850 })851 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;852853 Self::deposit_event(Event::PropertyPermissionSet(854 collection.id,855 property_permission,856 ));857858 Ok(())859 }860861 pub fn set_property_permissions(862 collection: &CollectionHandle<T>,863 sender: &T::CrossAccountId,864 property_permissions: Vec<PropertyKeyPermission>,865 ) -> DispatchResult {866 for prop_pemission in property_permissions {867 Self::set_property_permission(collection, sender, prop_pemission)?;868 }869870 Ok(())871 }872873 pub fn bytes_keys_to_property_keys(874 keys: Vec<Vec<u8>>,875 ) -> Result<Vec<PropertyKey>, DispatchError> {876 keys.into_iter()877 .map(|key| -> Result<PropertyKey, DispatchError> {878 key.try_into()879 .map_err(|_| <Error<T>>::UnableToReadUnboundedKeys.into())880 })881 .collect::<Result<Vec<PropertyKey>, DispatchError>>()882 }883884 pub fn check_property_key(key: &PropertyKey) -> Result<(), DispatchError> {885 let key_str = sp_std::str::from_utf8(key.as_slice())886 .map_err(|_| <Error<T>>::InvalidCharacterInPropertyKey)?;887888 for ch in key_str.chars() {889 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-' {890 return Err(<Error<T>>::InvalidCharacterInPropertyKey.into());891 }892 }893894 Ok(())895 }896897 pub fn filter_collection_properties(898 collection_id: CollectionId,899 keys: Vec<PropertyKey>,900 ) -> Result<Vec<Property>, DispatchError> {901 let properties = Self::collection_properties(collection_id);902903 let properties = keys904 .into_iter()905 .filter_map(|key| {906 properties.get(&key)907 .map(|value| Property {908 key,909 value: value.clone(),910 })911 })912 .collect();913914 Ok(properties)915 }916917 pub fn filter_property_permissions(918 collection_id: CollectionId,919 keys: Vec<PropertyKey>,920 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {921 let permissions = Self::property_permissions(collection_id);922923 let key_permissions = keys924 .into_iter()925 .filter_map(|key| {926 permissions927 .get(&key)928 .map(|permission| PropertyKeyPermission {929 key,930 permission: permission.clone(),931 })932 })933 .collect();934935 Ok(key_permissions)936 }937938 fn set_field_raw(939 collection_id: CollectionId,940 field: CollectionField,941 value: Vec<u8>,942 ) -> DispatchResult {943 if !value.is_empty() {944 <CollectionData<T>>::insert(945 (collection_id, field),946 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,947 )948 } else {949 <CollectionData<T>>::remove((collection_id, field));950 }951 Ok(())952 }953954 pub fn set_field(955 collection: &CollectionHandle<T>,956 sender: &T::CrossAccountId,957 field: CollectionField,958 value: Vec<u8>,959 ) -> DispatchResult {960 collection.check_is_owner_or_admin(sender)?;961962 963964 Self::set_field_raw(collection.id, field, value)965 }966967 pub fn toggle_allowlist(968 collection: &CollectionHandle<T>,969 sender: &T::CrossAccountId,970 user: &T::CrossAccountId,971 allowed: bool,972 ) -> DispatchResult {973 collection.check_is_owner_or_admin(sender)?;974975 976977 if allowed {978 <Allowlist<T>>::insert((collection.id, user), true);979 } else {980 <Allowlist<T>>::remove((collection.id, user));981 }982983 Ok(())984 }985986 pub fn toggle_admin(987 collection: &CollectionHandle<T>,988 sender: &T::CrossAccountId,989 user: &T::CrossAccountId,990 admin: bool,991 ) -> DispatchResult {992 collection.check_is_owner_or_admin(sender)?;993994 let was_admin = <IsAdmin<T>>::get((collection.id, user));995 if was_admin == admin {996 return Ok(());997 }998 let amount = <AdminAmount<T>>::get(collection.id);9991000 if admin {1001 let amount = amount1002 .checked_add(1)1003 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1004 ensure!(1005 amount <= Self::collection_admins_limit(),1006 <Error<T>>::CollectionAdminCountExceeded,1007 );10081009 10101011 <AdminAmount<T>>::insert(collection.id, amount);1012 <IsAdmin<T>>::insert((collection.id, user), true);1013 } else {1014 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1015 <IsAdmin<T>>::remove((collection.id, user));1016 }10171018 Ok(())1019 }10201021 pub fn clamp_limits(1022 mode: CollectionMode,1023 old_limit: &CollectionLimits,1024 mut new_limit: CollectionLimits,1025 ) -> Result<CollectionLimits, DispatchError> {1026 macro_rules! limit_default {1027 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1028 $(1029 if let Some($new) = $new.$field {1030 let $old = $old.$field($($arg)?);1031 let _ = $new;1032 let _ = $old;1033 $check1034 } else {1035 $new.$field = $old.$field1036 }1037 )*1038 }};1039 }10401041 limit_default!(old_limit, new_limit,1042 account_token_ownership_limit => ensure!(1043 new_limit <= MAX_TOKEN_OWNERSHIP,1044 <Error<T>>::CollectionLimitBoundsExceeded,1045 ),1046 sponsor_transfer_timeout(match mode {1047 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1048 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1049 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1050 }) => ensure!(1051 new_limit <= MAX_SPONSOR_TIMEOUT,1052 <Error<T>>::CollectionLimitBoundsExceeded,1053 ),1054 sponsored_data_size => ensure!(1055 new_limit <= CUSTOM_DATA_LIMIT,1056 <Error<T>>::CollectionLimitBoundsExceeded,1057 ),1058 token_limit => ensure!(1059 old_limit >= new_limit && new_limit > 0,1060 <Error<T>>::CollectionTokenLimitExceeded1061 ),1062 owner_can_transfer => ensure!(1063 old_limit || !new_limit,1064 <Error<T>>::OwnerPermissionsCantBeReverted,1065 ),1066 owner_can_destroy => ensure!(1067 old_limit || !new_limit,1068 <Error<T>>::OwnerPermissionsCantBeReverted,1069 ),1070 sponsored_data_rate_limit => {},1071 transfers_enabled => {},1072 );1073 Ok(new_limit)1074 }1075}10761077#[macro_export]1078macro_rules! unsupported {1079 () => {1080 Err(<Error<T>>::UnsupportedOperation.into())1081 };1082}108310841085pub trait CommonWeightInfo<CrossAccountId> {1086 fn create_item() -> Weight;1087 fn create_multiple_items(amount: u32) -> Weight;1088 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1089 fn burn_item() -> Weight;1090 fn set_collection_properties(amount: u32) -> Weight;1091 fn delete_collection_properties(amount: u32) -> Weight;1092 fn set_token_properties(amount: u32) -> Weight;1093 fn delete_token_properties(amount: u32) -> Weight;1094 fn set_property_permissions(amount: u32) -> Weight;1095 fn transfer() -> Weight;1096 fn approve() -> Weight;1097 fn transfer_from() -> Weight;1098 fn burn_from() -> Weight;1099 fn set_variable_metadata(bytes: u32) -> Weight;1100}11011102pub trait CommonCollectionOperations<T: Config> {1103 fn create_item(1104 &self,1105 sender: T::CrossAccountId,1106 to: T::CrossAccountId,1107 data: CreateItemData,1108 nesting_budget: &dyn Budget,1109 ) -> DispatchResultWithPostInfo;1110 fn create_multiple_items(1111 &self,1112 sender: T::CrossAccountId,1113 to: T::CrossAccountId,1114 data: Vec<CreateItemData>,1115 nesting_budget: &dyn Budget,1116 ) -> DispatchResultWithPostInfo;1117 fn create_multiple_items_ex(1118 &self,1119 sender: T::CrossAccountId,1120 data: CreateItemExData<T::CrossAccountId>,1121 nesting_budget: &dyn Budget,1122 ) -> DispatchResultWithPostInfo;1123 fn burn_item(1124 &self,1125 sender: T::CrossAccountId,1126 token: TokenId,1127 amount: u128,1128 ) -> DispatchResultWithPostInfo;1129 fn set_collection_properties(1130 &self,1131 sender: T::CrossAccountId,1132 properties: Vec<Property>,1133 ) -> DispatchResultWithPostInfo;1134 fn delete_collection_properties(1135 &self,1136 sender: &T::CrossAccountId,1137 property_keys: Vec<PropertyKey>,1138 ) -> DispatchResultWithPostInfo;1139 fn set_token_properties(1140 &self,1141 sender: T::CrossAccountId,1142 token_id: TokenId,1143 property: Vec<Property>,1144 ) -> DispatchResultWithPostInfo;1145 fn delete_token_properties(1146 &self,1147 sender: T::CrossAccountId,1148 token_id: TokenId,1149 property_keys: Vec<PropertyKey>,1150 ) -> DispatchResultWithPostInfo;1151 fn set_property_permissions(1152 &self,1153 sender: &T::CrossAccountId,1154 property_permissions: Vec<PropertyKeyPermission>,1155 ) -> DispatchResultWithPostInfo;1156 fn transfer(1157 &self,1158 sender: T::CrossAccountId,1159 to: T::CrossAccountId,1160 token: TokenId,1161 amount: u128,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn approve(1165 &self,1166 sender: T::CrossAccountId,1167 spender: T::CrossAccountId,1168 token: TokenId,1169 amount: u128,1170 ) -> DispatchResultWithPostInfo;1171 fn transfer_from(1172 &self,1173 sender: T::CrossAccountId,1174 from: T::CrossAccountId,1175 to: T::CrossAccountId,1176 token: TokenId,1177 amount: u128,1178 nesting_budget: &dyn Budget,1179 ) -> DispatchResultWithPostInfo;1180 fn burn_from(1181 &self,1182 sender: T::CrossAccountId,1183 from: T::CrossAccountId,1184 token: TokenId,1185 amount: u128,1186 nesting_budget: &dyn Budget,1187 ) -> DispatchResultWithPostInfo;11881189 fn set_variable_metadata(1190 &self,1191 sender: T::CrossAccountId,1192 token: TokenId,1193 data: BoundedVec<u8, CustomDataLimit>,1194 ) -> DispatchResultWithPostInfo;11951196 fn check_nesting(1197 &self,1198 sender: T::CrossAccountId,1199 from: (CollectionId, TokenId),1200 under: TokenId,1201 budget: &dyn Budget,1202 ) -> DispatchResult;12031204 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1205 fn collection_tokens(&self) -> Vec<TokenId>;1206 fn token_exists(&self, token: TokenId) -> bool;1207 fn last_token_id(&self) -> TokenId;12081209 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1210 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1211 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;1212 fn token_properties(&self, token_id: TokenId, keys: Vec<PropertyKey>) -> Vec<Property>;1213 1214 fn total_supply(&self) -> u32;1215 1216 fn account_balance(&self, account: T::CrossAccountId) -> u32;1217 1218 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1219 fn allowance(1220 &self,1221 sender: T::CrossAccountId,1222 spender: T::CrossAccountId,1223 token: TokenId,1224 ) -> u128;1225}122612271228pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1229 let post_info = PostDispatchInfo {1230 actual_weight: Some(weight),1231 pays_fee: Pays::Yes,1232 };1233 match res {1234 Ok(()) => Ok(post_info),1235 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1236 }1237}12381239impl<T: Config> From<PropertiesError> for Error<T> {1240 fn from(error: PropertiesError) -> Self {1241 match error {1242 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1243 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1244 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1245 }1246 }1247}