1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156157 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158 if self.collection.sponsorship.pending_sponsor() != Some(sender) {159 return false;160 };161162 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163 true164 }165}166impl<T: Config> Deref for CollectionHandle<T> {167 type Target = Collection<T::AccountId>;168169 fn deref(&self) -> &Self::Target {170 &self.collection171 }172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175 fn deref_mut(&mut self) -> &mut Self::Target {176 &mut self.collection177 }178}179180impl<T: Config> CollectionHandle<T> {181 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183 Ok(())184 }185 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187 }188 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190 Ok(())191 }192 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194 }195 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197 }198 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199 ensure!(200 <Allowlist<T>>::get((self.id, user)),201 <Error<T>>::AddressNotInAllowlist202 );203 Ok(())204 }205}206207#[frame_support::pallet]208pub mod pallet {209 use super::*;210 use pallet_evm::account;211 use dispatch::CollectionDispatch;212 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213 use frame_system::pallet_prelude::*;214 use frame_support::traits::Currency;215 use up_data_structs::{TokenId, mapping::TokenAddressMapping};216 use scale_info::TypeInfo;217 use weights::WeightInfo;218219 #[pallet::config]220 pub trait Config:221 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config222 {223 type WeightInfo: WeightInfo;224 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;225226 type Currency: Currency<Self::AccountId>;227228 #[pallet::constant]229 type CollectionCreationPrice: Get<230 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,231 >;232 type CollectionDispatch: CollectionDispatch<Self>;233234 type TreasuryAccountId: Get<Self::AccountId>;235236 type EvmTokenAddressMapping: TokenAddressMapping<H160>;237 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;238 }239240 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);241242 #[pallet::pallet]243 #[pallet::storage_version(STORAGE_VERSION)]244 #[pallet::generate_store(pub(super) trait Store)]245 pub struct Pallet<T>(_);246247 #[pallet::extra_constants]248 impl<T: Config> Pallet<T> {249 pub fn collection_admins_limit() -> u32 {250 COLLECTION_ADMINS_LIMIT251 }252 }253254 #[pallet::event]255 #[pallet::generate_deposit(pub fn deposit_event)]256 pub enum Event<T: Config> {257 258 259 260 261 262 263 264 265 266 CollectionCreated(CollectionId, u8, T::AccountId),267268 269 270 271 272 273 CollectionDestroyed(CollectionId),274275 276 277 278 279 280 281 282 283 284 285 286 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),287288 289 290 291 292 293 294 295 296 297 298 299 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),300301 302 303 304 305 306 307 308 309 310 311 312 Transfer(313 CollectionId,314 TokenId,315 T::CrossAccountId,316 T::CrossAccountId,317 u128,318 ),319320 321 322 323 324 325 326 327 328 329 Approved(330 CollectionId,331 TokenId,332 T::CrossAccountId,333 T::CrossAccountId,334 u128,335 ),336337 CollectionPropertySet(CollectionId, PropertyKey),338339 CollectionPropertyDeleted(CollectionId, PropertyKey),340341 TokenPropertySet(CollectionId, TokenId, PropertyKey),342343 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),344345 PropertyPermissionSet(CollectionId, PropertyKey),346 }347348 #[pallet::error]349 pub enum Error<T> {350 351 CollectionNotFound,352 353 MustBeTokenOwner,354 355 NoPermission,356 357 PublicMintingNotAllowed,358 359 AddressNotInAllowlist,360361 362 CollectionNameLimitExceeded,363 364 CollectionDescriptionLimitExceeded,365 366 CollectionTokenPrefixLimitExceeded,367 368 TotalCollectionsLimitExceeded,369 370 CollectionAdminCountExceeded,371 372 CollectionLimitBoundsExceeded,373 374 OwnerPermissionsCantBeReverted,375 376 TransferNotAllowed,377 378 AccountTokenLimitExceeded,379 380 CollectionTokenLimitExceeded,381 382 MetadataFlagFrozen,383384 385 TokenNotFound,386 387 TokenValueTooLow,388 389 ApprovedValueTooLow,390 391 CantApproveMoreThanOwned,392393 394 AddressIsZero,395 396 UnsupportedOperation,397398 399 NotSufficientFounds,400401 402 NestingIsDisabled,403 404 OnlyOwnerAllowedToNest,405 406 SourceCollectionIsNotAllowedToNest,407408 409 CollectionFieldSizeExceeded,410411 412 NoSpaceForProperty,413414 415 PropertyLimitReached,416417 418 PropertyKeyIsTooLong,419420 421 InvalidCharacterInPropertyKey,422423 424 EmptyPropertyKey,425 }426427 #[pallet::storage]428 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;429 #[pallet::storage]430 pub type DestroyedCollectionCount<T> =431 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;432433 434 #[pallet::storage]435 pub type CollectionById<T> = StorageMap<436 Hasher = Blake2_128Concat,437 Key = CollectionId,438 Value = Collection<<T as frame_system::Config>::AccountId>,439 QueryKind = OptionQuery,440 >;441442 443 #[pallet::storage]444 #[pallet::getter(fn collection_properties)]445 pub type CollectionProperties<T> = StorageMap<446 Hasher = Blake2_128Concat,447 Key = CollectionId,448 Value = Properties,449 QueryKind = ValueQuery,450 OnEmpty = up_data_structs::CollectionProperties,451 >;452453 #[pallet::storage]454 #[pallet::getter(fn property_permissions)]455 pub type CollectionPropertyPermissions<T> = StorageMap<456 Hasher = Blake2_128Concat,457 Key = CollectionId,458 Value = PropertiesPermissionMap,459 QueryKind = ValueQuery,460 >;461462 #[pallet::storage]463 pub type AdminAmount<T> = StorageMap<464 Hasher = Blake2_128Concat,465 Key = CollectionId,466 Value = u32,467 QueryKind = ValueQuery,468 >;469470 471 #[pallet::storage]472 pub type IsAdmin<T: Config> = StorageNMap<473 Key = (474 Key<Blake2_128Concat, CollectionId>,475 Key<Blake2_128Concat, T::CrossAccountId>,476 ),477 Value = bool,478 QueryKind = ValueQuery,479 >;480481 482 #[pallet::storage]483 pub type Allowlist<T: Config> = StorageNMap<484 Key = (485 Key<Blake2_128Concat, CollectionId>,486 Key<Blake2_128Concat, T::CrossAccountId>,487 ),488 Value = bool,489 QueryKind = ValueQuery,490 >;491492 493 #[pallet::storage]494 pub type DummyStorageValue<T: Config> = StorageValue<495 Value = (496 CollectionStats,497 CollectionId,498 TokenId,499 PhantomType<(500 TokenData<T::CrossAccountId>,501 RpcCollection<T::AccountId>,502503 504 RmrkCollectionInfo<T::AccountId>,505 RmrkInstanceInfo<T::AccountId>,506 RmrkResourceInfo,507 RmrkPropertyInfo,508 RmrkBaseInfo<T::AccountId>,509 RmrkPartType,510 RmrkTheme,511 RmrkNftChild,512 )>,513 ),514 QueryKind = OptionQuery,515 >;516517 #[pallet::hooks]518 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {519 fn on_runtime_upgrade() -> Weight {520 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {521 use up_data_structs::{CollectionVersion1, CollectionVersion2};522 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {523 let mut props = Vec::new();524 if !v.offchain_schema.is_empty() {525 props.push(Property {526 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),527 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),528 });529 }530 if !v.variable_on_chain_schema.is_empty() {531 props.push(Property {532 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),533 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),534 });535 }536 if !v.const_on_chain_schema.is_empty() {537 props.push(Property {538 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),539 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),540 });541 }542 props.push(Property {543 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),544 value: match v.schema_version {545 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),546 SchemaVersion::Unique => b"Unique".as_slice(),547 }.to_vec().try_into().unwrap(),548 });549 Self::set_scoped_collection_properties(550 id,551 PropertyScope::None,552 props.into_iter(),553 ).expect("existing data larger than properties");554 let mut new = CollectionVersion2::from(v.clone());555 new.permissions.access = Some(v.access);556 new.permissions.mint_mode = Some(v.mint_mode);557 Some(new)558 });559 }560561 0562 }563 }564}565566impl<T: Config> Pallet<T> {567 568 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {569 ensure!(570 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,571 <Error<T>>::AddressIsZero572 );573 Ok(())574 }575 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {576 <IsAdmin<T>>::iter_prefix((collection,))577 .map(|(a, _)| a)578 .collect()579 }580 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {581 <Allowlist<T>>::iter_prefix((collection,))582 .map(|(a, _)| a)583 .collect()584 }585 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {586 <Allowlist<T>>::get((collection, user))587 }588 pub fn collection_stats() -> CollectionStats {589 let created = <CreatedCollectionCount<T>>::get();590 let destroyed = <DestroyedCollectionCount<T>>::get();591 CollectionStats {592 created: created.0,593 destroyed: destroyed.0,594 alive: created.0 - destroyed.0,595 }596 }597598 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {599 let collection = <CollectionById<T>>::get(collection);600 if collection.is_none() {601 return None;602 }603604 let collection = collection.unwrap();605 let limits = collection.limits;606 let effective_limits = CollectionLimits {607 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),608 sponsored_data_size: Some(limits.sponsored_data_size()),609 sponsored_data_rate_limit: Some(610 limits611 .sponsored_data_rate_limit612 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),613 ),614 token_limit: Some(limits.token_limit()),615 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(616 match collection.mode {617 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,618 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,619 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,620 },621 )),622 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),623 owner_can_transfer: Some(limits.owner_can_transfer()),624 owner_can_destroy: Some(limits.owner_can_destroy()),625 transfers_enabled: Some(limits.transfers_enabled()),626 };627628 Some(effective_limits)629 }630631 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {632 let Collection {633 name,634 description,635 owner,636 mode,637 token_prefix,638 sponsorship,639 limits,640 permissions,641 } = <CollectionById<T>>::get(collection)?;642643 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)644 .into_iter()645 .map(|(key, permission)| PropertyKeyPermission {646 key,647 permission,648 })649 .collect();650651 let properties = <CollectionProperties<T>>::get(collection)652 .into_iter()653 .map(|(key, value)| Property {654 key,655 value,656 })657 .collect();658659 let permissions = CollectionPermissions {660 access: Some(permissions.access()),661 mint_mode: Some(permissions.mint_mode()),662 nesting: Some(permissions.nesting().clone()),663 };664665 Some(RpcCollection {666 name: name.into_inner(),667 description: description.into_inner(),668 owner,669 mode,670 token_prefix: token_prefix.into_inner(),671 sponsorship,672 limits,673 permissions,674 token_property_permissions,675 properties,676 })677 }678}679680macro_rules! limit_default {681 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{682 $(683 if let Some($new) = $new.$field {684 let $old = $old.$field($($arg)?);685 let _ = $new;686 let _ = $old;687 $check688 } else {689 $new.$field = $old.$field690 }691 )*692 }};693}694macro_rules! limit_default_clone {695 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{696 $(697 if let Some($new) = $new.$field.clone() {698 let $old = $old.$field($($arg)?);699 let _ = $new;700 let _ = $old;701 $check702 } else {703 $new.$field = $old.$field.clone()704 }705 )*706 }};707}708709impl<T: Config> Pallet<T> {710 pub fn init_collection(711 owner: T::AccountId,712 data: CreateCollectionData<T::AccountId>,713 ) -> Result<CollectionId, DispatchError> {714 {715 ensure!(716 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,717 Error::<T>::CollectionTokenPrefixLimitExceeded718 );719 }720721 let created_count = <CreatedCollectionCount<T>>::get()722 .0723 .checked_add(1)724 .ok_or(ArithmeticError::Overflow)?;725 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;726 let id = CollectionId(created_count);727728 729 ensure!(730 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,731 <Error<T>>::TotalCollectionsLimitExceeded732 );733734 735736 let collection = Collection {737 owner: owner.clone(),738 name: data.name,739 mode: data.mode.clone(),740 description: data.description,741 token_prefix: data.token_prefix,742 sponsorship: data743 .pending_sponsor744 .map(SponsorshipState::Unconfirmed)745 .unwrap_or_default(),746 limits: data747 .limits748 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))749 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,750 permissions: data751 .permissions752 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))753 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,754 };755756 let mut collection_properties = up_data_structs::CollectionProperties::get();757 collection_properties758 .try_set_from_iter(data.properties.into_iter())759 .map_err(<Error<T>>::from)?;760761 CollectionProperties::<T>::insert(id, collection_properties);762763 let mut token_props_permissions = PropertiesPermissionMap::new();764 token_props_permissions765 .try_set_from_iter(data.token_property_permissions.into_iter())766 .map_err(<Error<T>>::from)?;767768 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);769770 771 {772 let mut imbalance =773 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();774 imbalance.subsume(775 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(776 &T::TreasuryAccountId::get(),777 T::CollectionCreationPrice::get(),778 ),779 );780 <T as Config>::Currency::settle(781 &owner,782 imbalance,783 WithdrawReasons::TRANSFER,784 ExistenceRequirement::KeepAlive,785 )786 .map_err(|_| Error::<T>::NotSufficientFounds)?;787 }788789 <CreatedCollectionCount<T>>::put(created_count);790 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));791 <CollectionById<T>>::insert(id, collection);792 Ok(id)793 }794795 pub fn destroy_collection(796 collection: CollectionHandle<T>,797 sender: &T::CrossAccountId,798 ) -> DispatchResult {799 ensure!(800 collection.limits.owner_can_destroy(),801 <Error<T>>::NoPermission,802 );803 collection.check_is_owner(sender)?;804805 let destroyed_collections = <DestroyedCollectionCount<T>>::get()806 .0807 .checked_add(1)808 .ok_or(ArithmeticError::Overflow)?;809810 811812 <DestroyedCollectionCount<T>>::put(destroyed_collections);813 <CollectionById<T>>::remove(collection.id);814 <AdminAmount<T>>::remove(collection.id);815 <IsAdmin<T>>::remove_prefix((collection.id,), None);816 <Allowlist<T>>::remove_prefix((collection.id,), None);817 <CollectionProperties<T>>::remove(collection.id);818819 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));820 Ok(())821 }822823 pub fn set_collection_property(824 collection: &CollectionHandle<T>,825 sender: &T::CrossAccountId,826 property: Property,827 ) -> DispatchResult {828 collection.check_is_owner_or_admin(sender)?;829830 CollectionProperties::<T>::try_mutate(collection.id, |properties| {831 let property = property.clone();832 properties.try_set(property.key, property.value)833 })834 .map_err(<Error<T>>::from)?;835836 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));837838 Ok(())839 }840841 pub fn set_scoped_collection_property(842 collection_id: CollectionId,843 scope: PropertyScope,844 property: Property,845 ) -> DispatchResult {846 CollectionProperties::<T>::try_mutate(collection_id, |properties| {847 properties.try_scoped_set(scope, property.key, property.value)848 })849 .map_err(<Error<T>>::from)?;850851 Ok(())852 }853854 pub fn set_scoped_collection_properties(855 collection_id: CollectionId,856 scope: PropertyScope,857 properties: impl Iterator<Item = Property>,858 ) -> DispatchResult {859 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {860 stored_properties.try_scoped_set_from_iter(scope, properties)861 })862 .map_err(<Error<T>>::from)?;863864 Ok(())865 }866867 #[transactional]868 pub fn set_collection_properties(869 collection: &CollectionHandle<T>,870 sender: &T::CrossAccountId,871 properties: Vec<Property>,872 ) -> DispatchResult {873 for property in properties {874 Self::set_collection_property(collection, sender, property)?;875 }876877 Ok(())878 }879880 pub fn delete_collection_property(881 collection: &CollectionHandle<T>,882 sender: &T::CrossAccountId,883 property_key: PropertyKey,884 ) -> DispatchResult {885 collection.check_is_owner_or_admin(sender)?;886887 CollectionProperties::<T>::try_mutate(collection.id, |properties| {888 properties.remove(&property_key)889 })890 .map_err(<Error<T>>::from)?;891892 Self::deposit_event(Event::CollectionPropertyDeleted(893 collection.id,894 property_key,895 ));896897 Ok(())898 }899900 #[transactional]901 pub fn delete_collection_properties(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 property_keys: Vec<PropertyKey>,905 ) -> DispatchResult {906 for key in property_keys {907 Self::delete_collection_property(collection, sender, key)?;908 }909910 Ok(())911 }912913 914 pub fn set_property_permission_unchecked(915 collection: CollectionId,916 property_permission: PropertyKeyPermission,917 ) -> DispatchResult {918 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {919 permissions.try_set(property_permission.key, property_permission.permission)920 })921 .map_err(<Error<T>>::from)?;922 Ok(())923 }924925 pub fn set_property_permission(926 collection: &CollectionHandle<T>,927 sender: &T::CrossAccountId,928 property_permission: PropertyKeyPermission,929 ) -> DispatchResult {930 collection.check_is_owner_or_admin(sender)?;931932 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);933 let current_permission = all_permissions.get(&property_permission.key);934 if matches![935 current_permission,936 Some(PropertyPermission { mutable: false, .. })937 ] {938 return Err(<Error<T>>::NoPermission.into());939 }940941 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {942 let property_permission = property_permission.clone();943 permissions.try_set(property_permission.key, property_permission.permission)944 })945 .map_err(<Error<T>>::from)?;946947 Self::deposit_event(Event::PropertyPermissionSet(948 collection.id,949 property_permission.key,950 ));951952 Ok(())953 }954955 #[transactional]956 pub fn set_property_permissions(957 collection: &CollectionHandle<T>,958 sender: &T::CrossAccountId,959 property_permissions: Vec<PropertyKeyPermission>,960 ) -> DispatchResult {961 for prop_pemission in property_permissions {962 Self::set_property_permission(collection, sender, prop_pemission)?;963 }964965 Ok(())966 }967968 pub fn get_collection_property(969 collection_id: CollectionId,970 key: &PropertyKey,971 ) -> Option<PropertyValue> {972 Self::collection_properties(collection_id).get(key).cloned()973 }974975 pub fn bytes_keys_to_property_keys(976 keys: Vec<Vec<u8>>,977 ) -> Result<Vec<PropertyKey>, DispatchError> {978 keys.into_iter()979 .map(|key| -> Result<PropertyKey, DispatchError> {980 key.try_into()981 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())982 })983 .collect::<Result<Vec<PropertyKey>, DispatchError>>()984 }985986 pub fn filter_collection_properties(987 collection_id: CollectionId,988 keys: Option<Vec<PropertyKey>>,989 ) -> Result<Vec<Property>, DispatchError> {990 let properties = Self::collection_properties(collection_id);991992 let properties = keys993 .map(|keys| {994 keys.into_iter()995 .filter_map(|key| {996 properties.get(&key).map(|value| Property {997 key,998 value: value.clone(),999 })1000 })1001 .collect()1002 })1003 .unwrap_or_else(|| {1004 properties1005 .into_iter()1006 .map(|(key, value)| Property {1007 key,1008 value,1009 })1010 .collect()1011 });10121013 Ok(properties)1014 }10151016 pub fn filter_property_permissions(1017 collection_id: CollectionId,1018 keys: Option<Vec<PropertyKey>>,1019 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1020 let permissions = Self::property_permissions(collection_id);10211022 let key_permissions = keys1023 .map(|keys| {1024 keys.into_iter()1025 .filter_map(|key| {1026 permissions1027 .get(&key)1028 .map(|permission| PropertyKeyPermission {1029 key,1030 permission: permission.clone(),1031 })1032 })1033 .collect()1034 })1035 .unwrap_or_else(|| {1036 permissions1037 .into_iter()1038 .map(|(key, permission)| PropertyKeyPermission {1039 key,1040 permission,1041 })1042 .collect()1043 });10441045 Ok(key_permissions)1046 }10471048 pub fn toggle_allowlist(1049 collection: &CollectionHandle<T>,1050 sender: &T::CrossAccountId,1051 user: &T::CrossAccountId,1052 allowed: bool,1053 ) -> DispatchResult {1054 collection.check_is_owner_or_admin(sender)?;10551056 10571058 if allowed {1059 <Allowlist<T>>::insert((collection.id, user), true);1060 } else {1061 <Allowlist<T>>::remove((collection.id, user));1062 }10631064 Ok(())1065 }10661067 pub fn toggle_admin(1068 collection: &CollectionHandle<T>,1069 sender: &T::CrossAccountId,1070 user: &T::CrossAccountId,1071 admin: bool,1072 ) -> DispatchResult {1073 collection.check_is_owner_or_admin(sender)?;10741075 let was_admin = <IsAdmin<T>>::get((collection.id, user));1076 if was_admin == admin {1077 return Ok(());1078 }1079 let amount = <AdminAmount<T>>::get(collection.id);10801081 if admin {1082 let amount = amount1083 .checked_add(1)1084 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1085 ensure!(1086 amount <= Self::collection_admins_limit(),1087 <Error<T>>::CollectionAdminCountExceeded,1088 );10891090 10911092 <AdminAmount<T>>::insert(collection.id, amount);1093 <IsAdmin<T>>::insert((collection.id, user), true);1094 } else {1095 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1096 <IsAdmin<T>>::remove((collection.id, user));1097 }10981099 Ok(())1100 }11011102 pub fn clamp_limits(1103 mode: CollectionMode,1104 old_limit: &CollectionLimits,1105 mut new_limit: CollectionLimits,1106 ) -> Result<CollectionLimits, DispatchError> {1107 limit_default!(old_limit, new_limit,1108 account_token_ownership_limit => ensure!(1109 new_limit <= MAX_TOKEN_OWNERSHIP,1110 <Error<T>>::CollectionLimitBoundsExceeded,1111 ),1112 sponsor_transfer_timeout(match mode {1113 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1114 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1115 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1116 }) => ensure!(1117 new_limit <= MAX_SPONSOR_TIMEOUT,1118 <Error<T>>::CollectionLimitBoundsExceeded,1119 ),1120 sponsored_data_size => ensure!(1121 new_limit <= CUSTOM_DATA_LIMIT,1122 <Error<T>>::CollectionLimitBoundsExceeded,1123 ),1124 token_limit => ensure!(1125 old_limit >= new_limit && new_limit > 0,1126 <Error<T>>::CollectionTokenLimitExceeded1127 ),1128 owner_can_transfer => ensure!(1129 old_limit || !new_limit,1130 <Error<T>>::OwnerPermissionsCantBeReverted,1131 ),1132 owner_can_destroy => ensure!(1133 old_limit || !new_limit,1134 <Error<T>>::OwnerPermissionsCantBeReverted,1135 ),1136 sponsored_data_rate_limit => {},1137 transfers_enabled => {},1138 );1139 Ok(new_limit)1140 }1141 pub fn clamp_permissions(1142 mode: CollectionMode,1143 old_limit: &CollectionPermissions,1144 mut new_limit: CollectionPermissions,1145 ) -> Result<CollectionPermissions, DispatchError> {1146 limit_default_clone!(old_limit, new_limit,1147 );1148 Ok(new_limit)1149 }1150}11511152#[macro_export]1153macro_rules! unsupported {1154 () => {1155 Err(<Error<T>>::UnsupportedOperation.into())1156 };1157}115811591160pub trait CommonWeightInfo<CrossAccountId> {1161 fn create_item() -> Weight;1162 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1163 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1164 fn burn_item() -> Weight;1165 fn set_collection_properties(amount: u32) -> Weight;1166 fn delete_collection_properties(amount: u32) -> Weight;1167 fn set_token_properties(amount: u32) -> Weight;1168 fn delete_token_properties(amount: u32) -> Weight;1169 fn set_property_permissions(amount: u32) -> Weight;1170 fn transfer() -> Weight;1171 fn approve() -> Weight;1172 fn transfer_from() -> Weight;1173 fn burn_from() -> Weight;1174}11751176pub trait CommonCollectionOperations<T: Config> {1177 fn create_item(1178 &self,1179 sender: T::CrossAccountId,1180 to: T::CrossAccountId,1181 data: CreateItemData,1182 nesting_budget: &dyn Budget,1183 ) -> DispatchResultWithPostInfo;1184 fn create_multiple_items(1185 &self,1186 sender: T::CrossAccountId,1187 to: T::CrossAccountId,1188 data: Vec<CreateItemData>,1189 nesting_budget: &dyn Budget,1190 ) -> DispatchResultWithPostInfo;1191 fn create_multiple_items_ex(1192 &self,1193 sender: T::CrossAccountId,1194 data: CreateItemExData<T::CrossAccountId>,1195 nesting_budget: &dyn Budget,1196 ) -> DispatchResultWithPostInfo;1197 fn burn_item(1198 &self,1199 sender: T::CrossAccountId,1200 token: TokenId,1201 amount: u128,1202 ) -> DispatchResultWithPostInfo;1203 fn set_collection_properties(1204 &self,1205 sender: T::CrossAccountId,1206 properties: Vec<Property>,1207 ) -> DispatchResultWithPostInfo;1208 fn delete_collection_properties(1209 &self,1210 sender: &T::CrossAccountId,1211 property_keys: Vec<PropertyKey>,1212 ) -> DispatchResultWithPostInfo;1213 fn set_token_properties(1214 &self,1215 sender: T::CrossAccountId,1216 token_id: TokenId,1217 property: Vec<Property>,1218 ) -> DispatchResultWithPostInfo;1219 fn delete_token_properties(1220 &self,1221 sender: T::CrossAccountId,1222 token_id: TokenId,1223 property_keys: Vec<PropertyKey>,1224 ) -> DispatchResultWithPostInfo;1225 fn set_property_permissions(1226 &self,1227 sender: &T::CrossAccountId,1228 property_permissions: Vec<PropertyKeyPermission>,1229 ) -> DispatchResultWithPostInfo;1230 fn transfer(1231 &self,1232 sender: T::CrossAccountId,1233 to: T::CrossAccountId,1234 token: TokenId,1235 amount: u128,1236 nesting_budget: &dyn Budget,1237 ) -> DispatchResultWithPostInfo;1238 fn approve(1239 &self,1240 sender: T::CrossAccountId,1241 spender: T::CrossAccountId,1242 token: TokenId,1243 amount: u128,1244 ) -> DispatchResultWithPostInfo;1245 fn transfer_from(1246 &self,1247 sender: T::CrossAccountId,1248 from: T::CrossAccountId,1249 to: T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 nesting_budget: &dyn Budget,1253 ) -> DispatchResultWithPostInfo;1254 fn burn_from(1255 &self,1256 sender: T::CrossAccountId,1257 from: T::CrossAccountId,1258 token: TokenId,1259 amount: u128,1260 nesting_budget: &dyn Budget,1261 ) -> DispatchResultWithPostInfo;12621263 fn check_nesting(1264 &self,1265 sender: T::CrossAccountId,1266 from: (CollectionId, TokenId),1267 under: TokenId,1268 budget: &dyn Budget,1269 ) -> DispatchResult;12701271 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1272 fn collection_tokens(&self) -> Vec<TokenId>;1273 fn token_exists(&self, token: TokenId) -> bool;1274 fn last_token_id(&self) -> TokenId;12751276 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1277 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1278 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1279 1280 fn total_supply(&self) -> u32;1281 1282 fn account_balance(&self, account: T::CrossAccountId) -> u32;1283 1284 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1285 fn allowance(1286 &self,1287 sender: T::CrossAccountId,1288 spender: T::CrossAccountId,1289 token: TokenId,1290 ) -> u128;1291}129212931294pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1295 let post_info = PostDispatchInfo {1296 actual_weight: Some(weight),1297 pays_fee: Pays::Yes,1298 };1299 match res {1300 Ok(()) => Ok(post_info),1301 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1302 }1303}13041305impl<T: Config> From<PropertiesError> for Error<T> {1306 fn from(error: PropertiesError) -> Self {1307 match error {1308 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1309 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1310 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1311 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1312 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1313 }1314 }1315}