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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,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 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> Result<(), DispatchError> {152 self.check_is_mutable()?;153 <CollectionById<T>>::insert(self.id, self.collection);154 Ok(())155 }156157 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {158 self.check_is_mutable()?;159 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);160 Ok(())161 }162163 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {164 self.check_is_mutable()?;165166 if self.collection.sponsorship.pending_sponsor() != Some(sender) {167 return Ok(false);168 }169170 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());171 Ok(true)172 }173174 175 176 pub fn check_is_mutable(&self) -> DispatchResult {177 if self.external_collection {178 return Err(<Error<T>>::CollectionIsReadOnly)?;179 }180181 Ok(())182 }183}184185impl<T: Config> Deref for CollectionHandle<T> {186 type Target = Collection<T::AccountId>;187188 fn deref(&self) -> &Self::Target {189 &self.collection190 }191}192193impl<T: Config> DerefMut for CollectionHandle<T> {194 fn deref_mut(&mut self) -> &mut Self::Target {195 &mut self.collection196 }197}198199impl<T: Config> CollectionHandle<T> {200 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {201 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);202 Ok(())203 }204 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {205 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))206 }207 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {208 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);209 Ok(())210 }211 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {212 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)213 }214 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {215 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)216 }217 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {218 ensure!(219 <Allowlist<T>>::get((self.id, user)),220 <Error<T>>::AddressNotInAllowlist221 );222 Ok(())223 }224}225226#[frame_support::pallet]227pub mod pallet {228 use super::*;229 use pallet_evm::account;230 use dispatch::CollectionDispatch;231 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};232 use frame_system::pallet_prelude::*;233 use frame_support::traits::Currency;234 use up_data_structs::{TokenId, mapping::TokenAddressMapping};235 use scale_info::TypeInfo;236 use weights::WeightInfo;237238 #[pallet::config]239 pub trait Config:240 frame_system::Config241 + pallet_evm_coder_substrate::Config242 + pallet_evm::Config243 + TypeInfo244 + account::Config245 {246 type WeightInfo: WeightInfo;247 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;248249 type Currency: Currency<Self::AccountId>;250251 #[pallet::constant]252 type CollectionCreationPrice: Get<253 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254 >;255 type CollectionDispatch: CollectionDispatch<Self>;256257 type TreasuryAccountId: Get<Self::AccountId>;258 type ContractAddress: Get<H160>;259260 type EvmTokenAddressMapping: TokenAddressMapping<H160>;261 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;262 }263264 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);265266 #[pallet::pallet]267 #[pallet::storage_version(STORAGE_VERSION)]268 #[pallet::generate_store(pub(super) trait Store)]269 pub struct Pallet<T>(_);270271 #[pallet::extra_constants]272 impl<T: Config> Pallet<T> {273 pub fn collection_admins_limit() -> u32 {274 COLLECTION_ADMINS_LIMIT275 }276 }277278 #[pallet::event]279 #[pallet::generate_deposit(pub fn deposit_event)]280 pub enum Event<T: Config> {281 282 283 284 285 286 287 288 289 290 CollectionCreated(CollectionId, u8, T::AccountId),291292 293 294 295 296 297 CollectionDestroyed(CollectionId),298299 300 301 302 303 304 305 306 307 308 309 310 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),311312 313 314 315 316 317 318 319 320 321 322 323 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),324325 326 327 328 329 330 331 332 333 334 335 336 Transfer(337 CollectionId,338 TokenId,339 T::CrossAccountId,340 T::CrossAccountId,341 u128,342 ),343344 345 346 347 348 349 350 351 352 353 Approved(354 CollectionId,355 TokenId,356 T::CrossAccountId,357 T::CrossAccountId,358 u128,359 ),360361 CollectionPropertySet(CollectionId, PropertyKey),362363 CollectionPropertyDeleted(CollectionId, PropertyKey),364365 TokenPropertySet(CollectionId, TokenId, PropertyKey),366367 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),368369 PropertyPermissionSet(CollectionId, PropertyKey),370 }371372 #[pallet::error]373 pub enum Error<T> {374 375 CollectionNotFound,376 377 MustBeTokenOwner,378 379 NoPermission,380 381 CantDestroyNotEmptyCollection,382 383 PublicMintingNotAllowed,384 385 AddressNotInAllowlist,386387 388 CollectionNameLimitExceeded,389 390 CollectionDescriptionLimitExceeded,391 392 CollectionTokenPrefixLimitExceeded,393 394 TotalCollectionsLimitExceeded,395 396 CollectionAdminCountExceeded,397 398 CollectionLimitBoundsExceeded,399 400 OwnerPermissionsCantBeReverted,401 402 TransferNotAllowed,403 404 AccountTokenLimitExceeded,405 406 CollectionTokenLimitExceeded,407 408 MetadataFlagFrozen,409410 411 TokenNotFound,412 413 TokenValueTooLow,414 415 ApprovedValueTooLow,416 417 CantApproveMoreThanOwned,418419 420 AddressIsZero,421 422 UnsupportedOperation,423424 425 NotSufficientFounds,426427 428 NestingIsDisabled,429 430 OnlyOwnerAllowedToNest,431 432 SourceCollectionIsNotAllowedToNest,433434 435 CollectionFieldSizeExceeded,436437 438 NoSpaceForProperty,439440 441 PropertyLimitReached,442443 444 PropertyKeyIsTooLong,445446 447 InvalidCharacterInPropertyKey,448449 450 EmptyPropertyKey,451452 453 CollectionIsReadOnly,454 }455456 #[pallet::storage]457 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;458 #[pallet::storage]459 pub type DestroyedCollectionCount<T> =460 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;461462 463 #[pallet::storage]464 pub type CollectionById<T> = StorageMap<465 Hasher = Blake2_128Concat,466 Key = CollectionId,467 Value = Collection<<T as frame_system::Config>::AccountId>,468 QueryKind = OptionQuery,469 >;470471 472 #[pallet::storage]473 #[pallet::getter(fn collection_properties)]474 pub type CollectionProperties<T> = StorageMap<475 Hasher = Blake2_128Concat,476 Key = CollectionId,477 Value = Properties,478 QueryKind = ValueQuery,479 OnEmpty = up_data_structs::CollectionProperties,480 >;481482 #[pallet::storage]483 #[pallet::getter(fn property_permissions)]484 pub type CollectionPropertyPermissions<T> = StorageMap<485 Hasher = Blake2_128Concat,486 Key = CollectionId,487 Value = PropertiesPermissionMap,488 QueryKind = ValueQuery,489 >;490491 #[pallet::storage]492 pub type AdminAmount<T> = StorageMap<493 Hasher = Blake2_128Concat,494 Key = CollectionId,495 Value = u32,496 QueryKind = ValueQuery,497 >;498499 500 #[pallet::storage]501 pub type IsAdmin<T: Config> = StorageNMap<502 Key = (503 Key<Blake2_128Concat, CollectionId>,504 Key<Blake2_128Concat, T::CrossAccountId>,505 ),506 Value = bool,507 QueryKind = ValueQuery,508 >;509510 511 #[pallet::storage]512 pub type Allowlist<T: Config> = StorageNMap<513 Key = (514 Key<Blake2_128Concat, CollectionId>,515 Key<Blake2_128Concat, T::CrossAccountId>,516 ),517 Value = bool,518 QueryKind = ValueQuery,519 >;520521 522 #[pallet::storage]523 pub type DummyStorageValue<T: Config> = StorageValue<524 Value = (525 CollectionStats,526 CollectionId,527 TokenId,528 TokenChild,529 PhantomType<(530 TokenData<T::CrossAccountId>,531 RpcCollection<T::AccountId>,532 533 RmrkCollectionInfo<T::AccountId>,534 RmrkInstanceInfo<T::AccountId>,535 RmrkResourceInfo,536 RmrkPropertyInfo,537 RmrkBaseInfo<T::AccountId>,538 RmrkPartType,539 RmrkTheme,540 RmrkNftChild,541 )>,542 ),543 QueryKind = OptionQuery,544 >;545546 #[pallet::hooks]547 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {548 fn on_runtime_upgrade() -> Weight {549 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {550 use up_data_structs::{CollectionVersion1, CollectionVersion2};551 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {552 let mut props = Vec::new();553 if !v.offchain_schema.is_empty() {554 props.push(Property {555 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),556 value: v557 .offchain_schema558 .clone()559 .into_inner()560 .try_into()561 .expect("offchain schema too big"),562 });563 }564 if !v.variable_on_chain_schema.is_empty() {565 props.push(Property {566 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),567 value: v568 .variable_on_chain_schema569 .clone()570 .into_inner()571 .try_into()572 .expect("offchain schema too big"),573 });574 }575 if !v.const_on_chain_schema.is_empty() {576 props.push(Property {577 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),578 value: v579 .const_on_chain_schema580 .clone()581 .into_inner()582 .try_into()583 .expect("offchain schema too big"),584 });585 }586 props.push(Property {587 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),588 value: match v.schema_version {589 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),590 SchemaVersion::Unique => b"Unique".as_slice(),591 }592 .to_vec()593 .try_into()594 .unwrap(),595 });596 Self::set_scoped_collection_properties(597 id,598 PropertyScope::None,599 props.into_iter(),600 )601 .expect("existing data larger than properties");602 let mut new = CollectionVersion2::from(v.clone());603 new.permissions.access = Some(v.access);604 new.permissions.mint_mode = Some(v.mint_mode);605 Some(new)606 });607 }608609 0610 }611 }612}613614impl<T: Config> Pallet<T> {615 616 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {617 ensure!(618 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,619 <Error<T>>::AddressIsZero620 );621 Ok(())622 }623 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {624 <IsAdmin<T>>::iter_prefix((collection,))625 .map(|(a, _)| a)626 .collect()627 }628 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {629 <Allowlist<T>>::iter_prefix((collection,))630 .map(|(a, _)| a)631 .collect()632 }633 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {634 <Allowlist<T>>::get((collection, user))635 }636 pub fn collection_stats() -> CollectionStats {637 let created = <CreatedCollectionCount<T>>::get();638 let destroyed = <DestroyedCollectionCount<T>>::get();639 CollectionStats {640 created: created.0,641 destroyed: destroyed.0,642 alive: created.0 - destroyed.0,643 }644 }645646 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {647 let collection = <CollectionById<T>>::get(collection);648 if collection.is_none() {649 return None;650 }651652 let collection = collection.unwrap();653 let limits = collection.limits;654 let effective_limits = CollectionLimits {655 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),656 sponsored_data_size: Some(limits.sponsored_data_size()),657 sponsored_data_rate_limit: Some(658 limits659 .sponsored_data_rate_limit660 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),661 ),662 token_limit: Some(limits.token_limit()),663 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(664 match collection.mode {665 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,666 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,667 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,668 },669 )),670 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),671 owner_can_transfer: Some(limits.owner_can_transfer()),672 owner_can_destroy: Some(limits.owner_can_destroy()),673 transfers_enabled: Some(limits.transfers_enabled()),674 };675676 Some(effective_limits)677 }678679 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {680 let Collection {681 name,682 description,683 owner,684 mode,685 token_prefix,686 sponsorship,687 limits,688 permissions,689 external_collection,690 } = <CollectionById<T>>::get(collection)?;691692 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)693 .into_iter()694 .map(|(key, permission)| PropertyKeyPermission { key, permission })695 .collect();696697 let properties = <CollectionProperties<T>>::get(collection)698 .into_iter()699 .map(|(key, value)| Property { key, value })700 .collect();701702 let permissions = CollectionPermissions {703 access: Some(permissions.access()),704 mint_mode: Some(permissions.mint_mode()),705 nesting: Some(permissions.nesting().clone()),706 };707708 Some(RpcCollection {709 name: name.into_inner(),710 description: description.into_inner(),711 owner,712 mode,713 token_prefix: token_prefix.into_inner(),714 sponsorship,715 limits,716 permissions,717 token_property_permissions,718 properties,719 read_only: external_collection,720 })721 }722}723724macro_rules! limit_default {725 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{726 $(727 if let Some($new) = $new.$field {728 let $old = $old.$field($($arg)?);729 let _ = $new;730 let _ = $old;731 $check732 } else {733 $new.$field = $old.$field734 }735 )*736 }};737}738macro_rules! limit_default_clone {739 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{740 $(741 if let Some($new) = $new.$field.clone() {742 let $old = $old.$field($($arg)?);743 let _ = $new;744 let _ = $old;745 $check746 } else {747 $new.$field = $old.$field.clone()748 }749 )*750 }};751}752753impl<T: Config> Pallet<T> {754 pub fn init_collection(755 owner: T::CrossAccountId,756 data: CreateCollectionData<T::AccountId>,757 ) -> Result<CollectionId, DispatchError> {758 {759 ensure!(760 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,761 Error::<T>::CollectionTokenPrefixLimitExceeded762 );763 }764765 let created_count = <CreatedCollectionCount<T>>::get()766 .0767 .checked_add(1)768 .ok_or(ArithmeticError::Overflow)?;769 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;770 let id = CollectionId(created_count);771772 773 ensure!(774 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,775 <Error<T>>::TotalCollectionsLimitExceeded776 );777778 779780 let collection = Collection {781 owner: owner.as_sub().clone(),782 name: data.name,783 mode: data.mode.clone(),784 description: data.description,785 token_prefix: data.token_prefix,786 sponsorship: data787 .pending_sponsor788 .map(SponsorshipState::Unconfirmed)789 .unwrap_or_default(),790 limits: data791 .limits792 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))793 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,794 permissions: data795 .permissions796 .map(|permissions| {797 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)798 })799 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,800 external_collection: false,801 };802803 let mut collection_properties = up_data_structs::CollectionProperties::get();804 collection_properties805 .try_set_from_iter(data.properties.into_iter())806 .map_err(<Error<T>>::from)?;807808 CollectionProperties::<T>::insert(id, collection_properties);809810 let mut token_props_permissions = PropertiesPermissionMap::new();811 token_props_permissions812 .try_set_from_iter(data.token_property_permissions.into_iter())813 .map_err(<Error<T>>::from)?;814815 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);816817 818 {819 let mut imbalance =820 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();821 imbalance.subsume(822 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(823 &T::TreasuryAccountId::get(),824 T::CollectionCreationPrice::get(),825 ),826 );827 <T as Config>::Currency::settle(828 &owner.as_sub(),829 imbalance,830 WithdrawReasons::TRANSFER,831 ExistenceRequirement::KeepAlive,832 )833 .map_err(|_| Error::<T>::NotSufficientFounds)?;834 }835836 <CreatedCollectionCount<T>>::put(created_count);837 <Pallet<T>>::deposit_event(Event::CollectionCreated(838 id,839 data.mode.id(),840 owner.as_sub().clone(),841 ));842 <PalletEvm<T>>::deposit_log(843 erc::CollectionHelpersEvents::CollectionCreated {844 owner: *owner.as_eth(),845 collection_id: eth::collection_id_to_address(id),846 }847 .to_log(T::ContractAddress::get()),848 );849 <CollectionById<T>>::insert(id, collection);850 Ok(id)851 }852853 pub fn destroy_collection(854 collection: CollectionHandle<T>,855 sender: &T::CrossAccountId,856 ) -> DispatchResult {857 collection.check_is_mutable()?;858 ensure!(859 collection.limits.owner_can_destroy(),860 <Error<T>>::NoPermission,861 );862 collection.check_is_owner(sender)?;863864 let destroyed_collections = <DestroyedCollectionCount<T>>::get()865 .0866 .checked_add(1)867 .ok_or(ArithmeticError::Overflow)?;868869 870871 <DestroyedCollectionCount<T>>::put(destroyed_collections);872 <CollectionById<T>>::remove(collection.id);873 <AdminAmount<T>>::remove(collection.id);874 <IsAdmin<T>>::remove_prefix((collection.id,), None);875 <Allowlist<T>>::remove_prefix((collection.id,), None);876 <CollectionProperties<T>>::remove(collection.id);877878 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));879 Ok(())880 }881882 pub fn set_collection_property(883 collection: &CollectionHandle<T>,884 sender: &T::CrossAccountId,885 property: Property,886 ) -> DispatchResult {887 collection.check_is_mutable()?;888 collection.check_is_owner_or_admin(sender)?;889890 CollectionProperties::<T>::try_mutate(collection.id, |properties| {891 let property = property.clone();892 properties.try_set(property.key, property.value)893 })894 .map_err(<Error<T>>::from)?;895896 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));897898 Ok(())899 }900901 pub fn set_scoped_collection_property(902 collection_id: CollectionId,903 scope: PropertyScope,904 property: Property,905 ) -> DispatchResult {906 CollectionProperties::<T>::try_mutate(collection_id, |properties| {907 properties.try_scoped_set(scope, property.key, property.value)908 })909 .map_err(<Error<T>>::from)?;910911 Ok(())912 }913914 pub fn set_scoped_collection_properties(915 collection_id: CollectionId,916 scope: PropertyScope,917 properties: impl Iterator<Item = Property>,918 ) -> DispatchResult {919 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {920 stored_properties.try_scoped_set_from_iter(scope, properties)921 })922 .map_err(<Error<T>>::from)?;923924 Ok(())925 }926927 #[transactional]928 pub fn set_collection_properties(929 collection: &CollectionHandle<T>,930 sender: &T::CrossAccountId,931 properties: Vec<Property>,932 ) -> DispatchResult {933 collection.check_is_mutable()?;934935 for property in properties {936 Self::set_collection_property(collection, sender, property)?;937 }938939 Ok(())940 }941942 pub fn delete_collection_property(943 collection: &CollectionHandle<T>,944 sender: &T::CrossAccountId,945 property_key: PropertyKey,946 ) -> DispatchResult {947 collection.check_is_mutable()?;948 collection.check_is_owner_or_admin(sender)?;949950 CollectionProperties::<T>::try_mutate(collection.id, |properties| {951 properties.remove(&property_key)952 })953 .map_err(<Error<T>>::from)?;954955 Self::deposit_event(Event::CollectionPropertyDeleted(956 collection.id,957 property_key,958 ));959960 Ok(())961 }962963 #[transactional]964 pub fn delete_collection_properties(965 collection: &CollectionHandle<T>,966 sender: &T::CrossAccountId,967 property_keys: Vec<PropertyKey>,968 ) -> DispatchResult {969 collection.check_is_mutable()?;970971 for key in property_keys {972 Self::delete_collection_property(collection, sender, key)?;973 }974975 Ok(())976 }977978 979 pub fn set_property_permission_unchecked(980 collection: CollectionId,981 property_permission: PropertyKeyPermission,982 ) -> DispatchResult {983 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {984 permissions.try_set(property_permission.key, property_permission.permission)985 })986 .map_err(<Error<T>>::from)?;987 Ok(())988 }989990 pub fn set_property_permission(991 collection: &CollectionHandle<T>,992 sender: &T::CrossAccountId,993 property_permission: PropertyKeyPermission,994 ) -> DispatchResult {995 collection.check_is_mutable()?;996 collection.check_is_owner_or_admin(sender)?;997998 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);999 let current_permission = all_permissions.get(&property_permission.key);1000 if matches![1001 current_permission,1002 Some(PropertyPermission { mutable: false, .. })1003 ] {1004 return Err(<Error<T>>::NoPermission.into());1005 }10061007 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1008 let property_permission = property_permission.clone();1009 permissions.try_set(property_permission.key, property_permission.permission)1010 })1011 .map_err(<Error<T>>::from)?;10121013 Self::deposit_event(Event::PropertyPermissionSet(1014 collection.id,1015 property_permission.key,1016 ));10171018 Ok(())1019 }10201021 #[transactional]1022 pub fn set_property_permissions(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 property_permissions: Vec<PropertyKeyPermission>,1026 ) -> DispatchResult {1027 collection.check_is_mutable()?;10281029 for prop_pemission in property_permissions {1030 Self::set_property_permission(collection, sender, prop_pemission)?;1031 }10321033 Ok(())1034 }10351036 pub fn get_collection_property(1037 collection_id: CollectionId,1038 key: &PropertyKey,1039 ) -> Option<PropertyValue> {1040 Self::collection_properties(collection_id).get(key).cloned()1041 }10421043 pub fn bytes_keys_to_property_keys(1044 keys: Vec<Vec<u8>>,1045 ) -> Result<Vec<PropertyKey>, DispatchError> {1046 keys.into_iter()1047 .map(|key| -> Result<PropertyKey, DispatchError> {1048 key.try_into()1049 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1050 })1051 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1052 }10531054 pub fn filter_collection_properties(1055 collection_id: CollectionId,1056 keys: Option<Vec<PropertyKey>>,1057 ) -> Result<Vec<Property>, DispatchError> {1058 let properties = Self::collection_properties(collection_id);10591060 let properties = keys1061 .map(|keys| {1062 keys.into_iter()1063 .filter_map(|key| {1064 properties.get(&key).map(|value| Property {1065 key,1066 value: value.clone(),1067 })1068 })1069 .collect()1070 })1071 .unwrap_or_else(|| {1072 properties1073 .into_iter()1074 .map(|(key, value)| Property { key, value })1075 .collect()1076 });10771078 Ok(properties)1079 }10801081 pub fn filter_property_permissions(1082 collection_id: CollectionId,1083 keys: Option<Vec<PropertyKey>>,1084 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1085 let permissions = Self::property_permissions(collection_id);10861087 let key_permissions = keys1088 .map(|keys| {1089 keys.into_iter()1090 .filter_map(|key| {1091 permissions1092 .get(&key)1093 .map(|permission| PropertyKeyPermission {1094 key,1095 permission: permission.clone(),1096 })1097 })1098 .collect()1099 })1100 .unwrap_or_else(|| {1101 permissions1102 .into_iter()1103 .map(|(key, permission)| PropertyKeyPermission { key, permission })1104 .collect()1105 });11061107 Ok(key_permissions)1108 }11091110 pub fn toggle_allowlist(1111 collection: &CollectionHandle<T>,1112 sender: &T::CrossAccountId,1113 user: &T::CrossAccountId,1114 allowed: bool,1115 ) -> DispatchResult {1116 collection.check_is_mutable()?;1117 collection.check_is_owner_or_admin(sender)?;11181119 11201121 if allowed {1122 <Allowlist<T>>::insert((collection.id, user), true);1123 } else {1124 <Allowlist<T>>::remove((collection.id, user));1125 }11261127 Ok(())1128 }11291130 pub fn toggle_admin(1131 collection: &CollectionHandle<T>,1132 sender: &T::CrossAccountId,1133 user: &T::CrossAccountId,1134 admin: bool,1135 ) -> DispatchResult {1136 collection.check_is_mutable()?;1137 collection.check_is_owner_or_admin(sender)?;11381139 let was_admin = <IsAdmin<T>>::get((collection.id, user));1140 if was_admin == admin {1141 return Ok(());1142 }1143 let amount = <AdminAmount<T>>::get(collection.id);11441145 if admin {1146 let amount = amount1147 .checked_add(1)1148 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1149 ensure!(1150 amount <= Self::collection_admins_limit(),1151 <Error<T>>::CollectionAdminCountExceeded,1152 );11531154 11551156 <AdminAmount<T>>::insert(collection.id, amount);1157 <IsAdmin<T>>::insert((collection.id, user), true);1158 } else {1159 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1160 <IsAdmin<T>>::remove((collection.id, user));1161 }11621163 Ok(())1164 }11651166 pub fn clamp_limits(1167 mode: CollectionMode,1168 old_limit: &CollectionLimits,1169 mut new_limit: CollectionLimits,1170 ) -> Result<CollectionLimits, DispatchError> {1171 limit_default!(old_limit, new_limit,1172 account_token_ownership_limit => ensure!(1173 new_limit <= MAX_TOKEN_OWNERSHIP,1174 <Error<T>>::CollectionLimitBoundsExceeded,1175 ),1176 sponsored_data_size => ensure!(1177 new_limit <= CUSTOM_DATA_LIMIT,1178 <Error<T>>::CollectionLimitBoundsExceeded,1179 ),11801181 sponsored_data_rate_limit => {},1182 token_limit => ensure!(1183 old_limit >= new_limit && new_limit > 0,1184 <Error<T>>::CollectionTokenLimitExceeded1185 ),11861187 sponsor_transfer_timeout(match mode {1188 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1189 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1190 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1191 }) => ensure!(1192 new_limit <= MAX_SPONSOR_TIMEOUT,1193 <Error<T>>::CollectionLimitBoundsExceeded,1194 ),1195 sponsor_approve_timeout => {},1196 owner_can_transfer => ensure!(1197 old_limit || !new_limit,1198 <Error<T>>::OwnerPermissionsCantBeReverted,1199 ),1200 owner_can_destroy => ensure!(1201 old_limit || !new_limit,1202 <Error<T>>::OwnerPermissionsCantBeReverted,1203 ),1204 transfers_enabled => {},1205 );1206 Ok(new_limit)1207 }12081209 pub fn clamp_permissions(1210 _mode: CollectionMode,1211 old_limit: &CollectionPermissions,1212 mut new_limit: CollectionPermissions,1213 ) -> Result<CollectionPermissions, DispatchError> {1214 limit_default_clone!(old_limit, new_limit,1215 access => {},1216 mint_mode => {},1217 nesting => {},1218 );1219 Ok(new_limit)1220 }1221}12221223#[macro_export]1224macro_rules! unsupported {1225 () => {1226 Err(<Error<T>>::UnsupportedOperation.into())1227 };1228}122912301231pub trait CommonWeightInfo<CrossAccountId> {1232 fn create_item() -> Weight;1233 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1234 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1235 fn burn_item() -> Weight;1236 fn set_collection_properties(amount: u32) -> Weight;1237 fn delete_collection_properties(amount: u32) -> Weight;1238 fn set_token_properties(amount: u32) -> Weight;1239 fn delete_token_properties(amount: u32) -> Weight;1240 fn set_property_permissions(amount: u32) -> Weight;1241 fn transfer() -> Weight;1242 fn approve() -> Weight;1243 fn transfer_from() -> Weight;1244 fn burn_from() -> Weight;12451246 1247 1248 1249 1250 fn burn_recursively_self_raw() -> Weight;1251 1252 1253 1254 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12551256 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1257 Self::burn_recursively_self_raw()1258 .saturating_mul(max_selfs.max(1) as u64)1259 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1260 }1261}12621263pub trait CommonCollectionOperations<T: Config> {1264 fn create_item(1265 &self,1266 sender: T::CrossAccountId,1267 to: T::CrossAccountId,1268 data: CreateItemData,1269 nesting_budget: &dyn Budget,1270 ) -> DispatchResultWithPostInfo;1271 fn create_multiple_items(1272 &self,1273 sender: T::CrossAccountId,1274 to: T::CrossAccountId,1275 data: Vec<CreateItemData>,1276 nesting_budget: &dyn Budget,1277 ) -> DispatchResultWithPostInfo;1278 fn create_multiple_items_ex(1279 &self,1280 sender: T::CrossAccountId,1281 data: CreateItemExData<T::CrossAccountId>,1282 nesting_budget: &dyn Budget,1283 ) -> DispatchResultWithPostInfo;1284 fn burn_item(1285 &self,1286 sender: T::CrossAccountId,1287 token: TokenId,1288 amount: u128,1289 ) -> DispatchResultWithPostInfo;1290 fn burn_item_recursively(1291 &self,1292 sender: T::CrossAccountId,1293 token: TokenId,1294 self_budget: &dyn Budget,1295 breadth_budget: &dyn Budget,1296 ) -> DispatchResultWithPostInfo;1297 fn set_collection_properties(1298 &self,1299 sender: T::CrossAccountId,1300 properties: Vec<Property>,1301 ) -> DispatchResultWithPostInfo;1302 fn delete_collection_properties(1303 &self,1304 sender: &T::CrossAccountId,1305 property_keys: Vec<PropertyKey>,1306 ) -> DispatchResultWithPostInfo;1307 fn set_token_properties(1308 &self,1309 sender: T::CrossAccountId,1310 token_id: TokenId,1311 property: Vec<Property>,1312 ) -> DispatchResultWithPostInfo;1313 fn delete_token_properties(1314 &self,1315 sender: T::CrossAccountId,1316 token_id: TokenId,1317 property_keys: Vec<PropertyKey>,1318 ) -> DispatchResultWithPostInfo;1319 fn set_property_permissions(1320 &self,1321 sender: &T::CrossAccountId,1322 property_permissions: Vec<PropertyKeyPermission>,1323 ) -> DispatchResultWithPostInfo;1324 fn transfer(1325 &self,1326 sender: T::CrossAccountId,1327 to: T::CrossAccountId,1328 token: TokenId,1329 amount: u128,1330 nesting_budget: &dyn Budget,1331 ) -> DispatchResultWithPostInfo;1332 fn approve(1333 &self,1334 sender: T::CrossAccountId,1335 spender: T::CrossAccountId,1336 token: TokenId,1337 amount: u128,1338 ) -> DispatchResultWithPostInfo;1339 fn transfer_from(1340 &self,1341 sender: T::CrossAccountId,1342 from: T::CrossAccountId,1343 to: T::CrossAccountId,1344 token: TokenId,1345 amount: u128,1346 nesting_budget: &dyn Budget,1347 ) -> DispatchResultWithPostInfo;1348 fn burn_from(1349 &self,1350 sender: T::CrossAccountId,1351 from: T::CrossAccountId,1352 token: TokenId,1353 amount: u128,1354 nesting_budget: &dyn Budget,1355 ) -> DispatchResultWithPostInfo;13561357 fn check_nesting(1358 &self,1359 sender: T::CrossAccountId,1360 from: (CollectionId, TokenId),1361 under: TokenId,1362 budget: &dyn Budget,1363 ) -> DispatchResult;13641365 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13681369 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1370 fn collection_tokens(&self) -> Vec<TokenId>;1371 fn token_exists(&self, token: TokenId) -> bool;1372 fn last_token_id(&self) -> TokenId;13731374 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1375 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1376 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1377 1378 fn total_supply(&self) -> u32;1379 1380 fn account_balance(&self, account: T::CrossAccountId) -> u32;1381 1382 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1383 fn allowance(1384 &self,1385 sender: T::CrossAccountId,1386 spender: T::CrossAccountId,1387 token: TokenId,1388 ) -> u128;1389}139013911392pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1393 let post_info = PostDispatchInfo {1394 actual_weight: Some(weight),1395 pays_fee: Pays::Yes,1396 };1397 match res {1398 Ok(()) => Ok(post_info),1399 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1400 }1401}14021403impl<T: Config> From<PropertiesError> for Error<T> {1404 fn from(error: PropertiesError) -> Self {1405 match error {1406 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1407 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1408 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1409 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1410 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1411 }1412 }1413}