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 RmrkBoundedTheme,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) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 171 172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 181 182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 288 289 290 291 292 293 294 CollectionCreated(CollectionId, u8, T::AccountId),295296 297 298 299 300 301 CollectionDestroyed(CollectionId),302303 304 305 306 307 308 309 310 311 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),312313 314 315 316 317 318 319 320 321 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),322323 324 325 326 327 328 329 330 331 332 Transfer(333 CollectionId,334 TokenId,335 T::CrossAccountId,336 T::CrossAccountId,337 u128,338 ),339340 341 342 343 344 345 346 347 348 349 Approved(350 CollectionId,351 TokenId,352 T::CrossAccountId,353 T::CrossAccountId,354 u128,355 ),356357 358 359 360 361 362 363 CollectionPropertySet(CollectionId, PropertyKey),364365 366 367 368 369 370 371 CollectionPropertyDeleted(CollectionId, PropertyKey),372373 374 375 376 377 378 379 380 TokenPropertySet(CollectionId, TokenId, PropertyKey),381382 383 384 385 386 387 388 389 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),390391 392 393 394 395 396 397 PropertyPermissionSet(CollectionId, PropertyKey),398 }399400 #[pallet::error]401 pub enum Error<T> {402 403 CollectionNotFound,404 405 MustBeTokenOwner,406 407 NoPermission,408 409 CantDestroyNotEmptyCollection,410 411 PublicMintingNotAllowed,412 413 AddressNotInAllowlist,414415 416 CollectionNameLimitExceeded,417 418 CollectionDescriptionLimitExceeded,419 420 CollectionTokenPrefixLimitExceeded,421 422 TotalCollectionsLimitExceeded,423 424 CollectionAdminCountExceeded,425 426 CollectionLimitBoundsExceeded,427 428 OwnerPermissionsCantBeReverted,429 430 TransferNotAllowed,431 432 AccountTokenLimitExceeded,433 434 CollectionTokenLimitExceeded,435 436 MetadataFlagFrozen,437438 439 TokenNotFound,440 441 TokenValueTooLow,442 443 ApprovedValueTooLow,444 445 CantApproveMoreThanOwned,446447 448 AddressIsZero,449 450 UnsupportedOperation,451452 453 NotSufficientFounds,454455 456 UserIsNotAllowedToNest,457 458 SourceCollectionIsNotAllowedToNest,459460 461 CollectionFieldSizeExceeded,462463 464 NoSpaceForProperty,465466 467 PropertyLimitReached,468469 470 PropertyKeyIsTooLong,471472 473 InvalidCharacterInPropertyKey,474475 476 EmptyPropertyKey,477478 479 CollectionIsExternal,480481 482 CollectionIsInternal,483 }484485 486 #[pallet::storage]487 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;488489 490 #[pallet::storage]491 pub type DestroyedCollectionCount<T> =492 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;493494 495 #[pallet::storage]496 pub type CollectionById<T> = StorageMap<497 Hasher = Blake2_128Concat,498 Key = CollectionId,499 Value = Collection<<T as frame_system::Config>::AccountId>,500 QueryKind = OptionQuery,501 >;502503 504 #[pallet::storage]505 #[pallet::getter(fn collection_properties)]506 pub type CollectionProperties<T> = StorageMap<507 Hasher = Blake2_128Concat,508 Key = CollectionId,509 Value = Properties,510 QueryKind = ValueQuery,511 OnEmpty = up_data_structs::CollectionProperties,512 >;513514 515 #[pallet::storage]516 #[pallet::getter(fn property_permissions)]517 pub type CollectionPropertyPermissions<T> = StorageMap<518 Hasher = Blake2_128Concat,519 Key = CollectionId,520 Value = PropertiesPermissionMap,521 QueryKind = ValueQuery,522 >;523524 525 #[pallet::storage]526 pub type AdminAmount<T> = StorageMap<527 Hasher = Blake2_128Concat,528 Key = CollectionId,529 Value = u32,530 QueryKind = ValueQuery,531 >;532533 534 #[pallet::storage]535 pub type IsAdmin<T: Config> = StorageNMap<536 Key = (537 Key<Blake2_128Concat, CollectionId>,538 Key<Blake2_128Concat, T::CrossAccountId>,539 ),540 Value = bool,541 QueryKind = ValueQuery,542 >;543544 545 #[pallet::storage]546 pub type Allowlist<T: Config> = StorageNMap<547 Key = (548 Key<Blake2_128Concat, CollectionId>,549 Key<Blake2_128Concat, T::CrossAccountId>,550 ),551 Value = bool,552 QueryKind = ValueQuery,553 >;554555 556 #[pallet::storage]557 pub type DummyStorageValue<T: Config> = StorageValue<558 Value = (559 CollectionStats,560 CollectionId,561 TokenId,562 TokenChild,563 PhantomType<(564 TokenData<T::CrossAccountId>,565 RpcCollection<T::AccountId>,566 567 RmrkCollectionInfo<T::AccountId>,568 RmrkInstanceInfo<T::AccountId>,569 RmrkResourceInfo,570 RmrkPropertyInfo,571 RmrkBaseInfo<T::AccountId>,572 RmrkPartType,573 RmrkBoundedTheme,574 RmrkNftChild,575 )>,576 ),577 QueryKind = OptionQuery,578 >;579580 #[pallet::hooks]581 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {582 fn on_runtime_upgrade() -> Weight {583 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {584 use up_data_structs::{CollectionVersion1, CollectionVersion2};585 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {586 let mut props = Vec::new();587 if !v.offchain_schema.is_empty() {588 props.push(Property {589 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),590 value: v591 .offchain_schema592 .clone()593 .into_inner()594 .try_into()595 .expect("offchain schema too big"),596 });597 }598 if !v.variable_on_chain_schema.is_empty() {599 props.push(Property {600 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),601 value: v602 .variable_on_chain_schema603 .clone()604 .into_inner()605 .try_into()606 .expect("offchain schema too big"),607 });608 }609 if !v.const_on_chain_schema.is_empty() {610 props.push(Property {611 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),612 value: v613 .const_on_chain_schema614 .clone()615 .into_inner()616 .try_into()617 .expect("offchain schema too big"),618 });619 }620 props.push(Property {621 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),622 value: match v.schema_version {623 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),624 SchemaVersion::Unique => b"Unique".as_slice(),625 }626 .to_vec()627 .try_into()628 .unwrap(),629 });630 Self::set_scoped_collection_properties(631 id,632 PropertyScope::None,633 props.into_iter(),634 )635 .expect("existing data larger than properties");636 let mut new = CollectionVersion2::from(v.clone());637 new.permissions.access = Some(v.access);638 new.permissions.mint_mode = Some(v.mint_mode);639 Some(new)640 });641 }642643 0644 }645 }646}647648impl<T: Config> Pallet<T> {649 650 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {651 ensure!(652 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,653 <Error<T>>::AddressIsZero654 );655 Ok(())656 }657 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {658 <IsAdmin<T>>::iter_prefix((collection,))659 .map(|(a, _)| a)660 .collect()661 }662 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {663 <Allowlist<T>>::iter_prefix((collection,))664 .map(|(a, _)| a)665 .collect()666 }667 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {668 <Allowlist<T>>::get((collection, user))669 }670 pub fn collection_stats() -> CollectionStats {671 let created = <CreatedCollectionCount<T>>::get();672 let destroyed = <DestroyedCollectionCount<T>>::get();673 CollectionStats {674 created: created.0,675 destroyed: destroyed.0,676 alive: created.0 - destroyed.0,677 }678 }679680 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {681 let collection = <CollectionById<T>>::get(collection);682 if collection.is_none() {683 return None;684 }685686 let collection = collection.unwrap();687 let limits = collection.limits;688 let effective_limits = CollectionLimits {689 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),690 sponsored_data_size: Some(limits.sponsored_data_size()),691 sponsored_data_rate_limit: Some(692 limits693 .sponsored_data_rate_limit694 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),695 ),696 token_limit: Some(limits.token_limit()),697 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(698 match collection.mode {699 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,700 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,701 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,702 },703 )),704 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),705 owner_can_transfer: Some(limits.owner_can_transfer()),706 owner_can_destroy: Some(limits.owner_can_destroy()),707 transfers_enabled: Some(limits.transfers_enabled()),708 };709710 Some(effective_limits)711 }712713 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {714 let Collection {715 name,716 description,717 owner,718 mode,719 token_prefix,720 sponsorship,721 limits,722 permissions,723 external_collection,724 } = <CollectionById<T>>::get(collection)?;725726 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)727 .into_iter()728 .map(|(key, permission)| PropertyKeyPermission { key, permission })729 .collect();730731 let properties = <CollectionProperties<T>>::get(collection)732 .into_iter()733 .map(|(key, value)| Property { key, value })734 .collect();735736 let permissions = CollectionPermissions {737 access: Some(permissions.access()),738 mint_mode: Some(permissions.mint_mode()),739 nesting: Some(permissions.nesting().clone()),740 };741742 Some(RpcCollection {743 name: name.into_inner(),744 description: description.into_inner(),745 owner,746 mode,747 token_prefix: token_prefix.into_inner(),748 sponsorship,749 limits,750 permissions,751 token_property_permissions,752 properties,753 read_only: external_collection,754 })755 }756}757758macro_rules! limit_default {759 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{760 $(761 if let Some($new) = $new.$field {762 let $old = $old.$field($($arg)?);763 let _ = $new;764 let _ = $old;765 $check766 } else {767 $new.$field = $old.$field768 }769 )*770 }};771}772macro_rules! limit_default_clone {773 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{774 $(775 if let Some($new) = $new.$field.clone() {776 let $old = $old.$field($($arg)?);777 let _ = $new;778 let _ = $old;779 $check780 } else {781 $new.$field = $old.$field.clone()782 }783 )*784 }};785}786787impl<T: Config> Pallet<T> {788 pub fn init_collection(789 owner: T::CrossAccountId,790 data: CreateCollectionData<T::AccountId>,791 is_external: bool,792 ) -> Result<CollectionId, DispatchError> {793 {794 ensure!(795 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,796 Error::<T>::CollectionTokenPrefixLimitExceeded797 );798 }799800 let created_count = <CreatedCollectionCount<T>>::get()801 .0802 .checked_add(1)803 .ok_or(ArithmeticError::Overflow)?;804 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;805 let id = CollectionId(created_count);806807 808 ensure!(809 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,810 <Error<T>>::TotalCollectionsLimitExceeded811 );812813 814815 let collection = Collection {816 owner: owner.as_sub().clone(),817 name: data.name,818 mode: data.mode.clone(),819 description: data.description,820 token_prefix: data.token_prefix,821 sponsorship: data822 .pending_sponsor823 .map(SponsorshipState::Unconfirmed)824 .unwrap_or_default(),825 limits: data826 .limits827 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))828 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,829 permissions: data830 .permissions831 .map(|permissions| {832 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)833 })834 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,835 external_collection: is_external,836 };837838 let mut collection_properties = up_data_structs::CollectionProperties::get();839 collection_properties840 .try_set_from_iter(data.properties.into_iter())841 .map_err(<Error<T>>::from)?;842843 CollectionProperties::<T>::insert(id, collection_properties);844845 let mut token_props_permissions = PropertiesPermissionMap::new();846 token_props_permissions847 .try_set_from_iter(data.token_property_permissions.into_iter())848 .map_err(<Error<T>>::from)?;849850 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);851852 853 {854 let mut imbalance =855 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();856 imbalance.subsume(857 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(858 &T::TreasuryAccountId::get(),859 T::CollectionCreationPrice::get(),860 ),861 );862 <T as Config>::Currency::settle(863 &owner.as_sub(),864 imbalance,865 WithdrawReasons::TRANSFER,866 ExistenceRequirement::KeepAlive,867 )868 .map_err(|_| Error::<T>::NotSufficientFounds)?;869 }870871 <CreatedCollectionCount<T>>::put(created_count);872 <Pallet<T>>::deposit_event(Event::CollectionCreated(873 id,874 data.mode.id(),875 owner.as_sub().clone(),876 ));877 <PalletEvm<T>>::deposit_log(878 erc::CollectionHelpersEvents::CollectionCreated {879 owner: *owner.as_eth(),880 collection_id: eth::collection_id_to_address(id),881 }882 .to_log(T::ContractAddress::get()),883 );884 <CollectionById<T>>::insert(id, collection);885 Ok(id)886 }887888 pub fn destroy_collection(889 collection: CollectionHandle<T>,890 sender: &T::CrossAccountId,891 ) -> DispatchResult {892 ensure!(893 collection.limits.owner_can_destroy(),894 <Error<T>>::NoPermission,895 );896 collection.check_is_owner(sender)?;897898 let destroyed_collections = <DestroyedCollectionCount<T>>::get()899 .0900 .checked_add(1)901 .ok_or(ArithmeticError::Overflow)?;902903 904905 <DestroyedCollectionCount<T>>::put(destroyed_collections);906 <CollectionById<T>>::remove(collection.id);907 <AdminAmount<T>>::remove(collection.id);908 <IsAdmin<T>>::remove_prefix((collection.id,), None);909 <Allowlist<T>>::remove_prefix((collection.id,), None);910 <CollectionProperties<T>>::remove(collection.id);911912 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));913 Ok(())914 }915916 pub fn set_collection_property(917 collection: &CollectionHandle<T>,918 sender: &T::CrossAccountId,919 property: Property,920 ) -> DispatchResult {921 collection.check_is_owner_or_admin(sender)?;922923 CollectionProperties::<T>::try_mutate(collection.id, |properties| {924 let property = property.clone();925 properties.try_set(property.key, property.value)926 })927 .map_err(<Error<T>>::from)?;928929 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));930931 Ok(())932 }933934 pub fn set_scoped_collection_property(935 collection_id: CollectionId,936 scope: PropertyScope,937 property: Property,938 ) -> DispatchResult {939 CollectionProperties::<T>::try_mutate(collection_id, |properties| {940 properties.try_scoped_set(scope, property.key, property.value)941 })942 .map_err(<Error<T>>::from)?;943944 Ok(())945 }946947 pub fn set_scoped_collection_properties(948 collection_id: CollectionId,949 scope: PropertyScope,950 properties: impl Iterator<Item = Property>,951 ) -> DispatchResult {952 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {953 stored_properties.try_scoped_set_from_iter(scope, properties)954 })955 .map_err(<Error<T>>::from)?;956957 Ok(())958 }959960 #[transactional]961 pub fn set_collection_properties(962 collection: &CollectionHandle<T>,963 sender: &T::CrossAccountId,964 properties: Vec<Property>,965 ) -> DispatchResult {966 for property in properties {967 Self::set_collection_property(collection, sender, property)?;968 }969970 Ok(())971 }972973 pub fn delete_collection_property(974 collection: &CollectionHandle<T>,975 sender: &T::CrossAccountId,976 property_key: PropertyKey,977 ) -> DispatchResult {978 collection.check_is_owner_or_admin(sender)?;979980 CollectionProperties::<T>::try_mutate(collection.id, |properties| {981 properties.remove(&property_key)982 })983 .map_err(<Error<T>>::from)?;984985 Self::deposit_event(Event::CollectionPropertyDeleted(986 collection.id,987 property_key,988 ));989990 Ok(())991 }992993 #[transactional]994 pub fn delete_collection_properties(995 collection: &CollectionHandle<T>,996 sender: &T::CrossAccountId,997 property_keys: Vec<PropertyKey>,998 ) -> DispatchResult {999 for key in property_keys {1000 Self::delete_collection_property(collection, sender, key)?;1001 }10021003 Ok(())1004 }10051006 1007 pub fn set_property_permission_unchecked(1008 collection: CollectionId,1009 property_permission: PropertyKeyPermission,1010 ) -> DispatchResult {1011 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1012 permissions.try_set(property_permission.key, property_permission.permission)1013 })1014 .map_err(<Error<T>>::from)?;1015 Ok(())1016 }10171018 pub fn set_property_permission(1019 collection: &CollectionHandle<T>,1020 sender: &T::CrossAccountId,1021 property_permission: PropertyKeyPermission,1022 ) -> DispatchResult {1023 collection.check_is_owner_or_admin(sender)?;10241025 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1026 let current_permission = all_permissions.get(&property_permission.key);1027 if matches![1028 current_permission,1029 Some(PropertyPermission { mutable: false, .. })1030 ] {1031 return Err(<Error<T>>::NoPermission.into());1032 }10331034 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1035 let property_permission = property_permission.clone();1036 permissions.try_set(property_permission.key, property_permission.permission)1037 })1038 .map_err(<Error<T>>::from)?;10391040 Self::deposit_event(Event::PropertyPermissionSet(1041 collection.id,1042 property_permission.key,1043 ));10441045 Ok(())1046 }10471048 #[transactional]1049 pub fn set_token_property_permissions(1050 collection: &CollectionHandle<T>,1051 sender: &T::CrossAccountId,1052 property_permissions: Vec<PropertyKeyPermission>,1053 ) -> DispatchResult {1054 for prop_pemission in property_permissions {1055 Self::set_property_permission(collection, sender, prop_pemission)?;1056 }10571058 Ok(())1059 }10601061 pub fn get_collection_property(1062 collection_id: CollectionId,1063 key: &PropertyKey,1064 ) -> Option<PropertyValue> {1065 Self::collection_properties(collection_id).get(key).cloned()1066 }10671068 pub fn bytes_keys_to_property_keys(1069 keys: Vec<Vec<u8>>,1070 ) -> Result<Vec<PropertyKey>, DispatchError> {1071 keys.into_iter()1072 .map(|key| -> Result<PropertyKey, DispatchError> {1073 key.try_into()1074 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1075 })1076 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1077 }10781079 pub fn filter_collection_properties(1080 collection_id: CollectionId,1081 keys: Option<Vec<PropertyKey>>,1082 ) -> Result<Vec<Property>, DispatchError> {1083 let properties = Self::collection_properties(collection_id);10841085 let properties = keys1086 .map(|keys| {1087 keys.into_iter()1088 .filter_map(|key| {1089 properties.get(&key).map(|value| Property {1090 key,1091 value: value.clone(),1092 })1093 })1094 .collect()1095 })1096 .unwrap_or_else(|| {1097 properties1098 .into_iter()1099 .map(|(key, value)| Property { key, value })1100 .collect()1101 });11021103 Ok(properties)1104 }11051106 pub fn filter_property_permissions(1107 collection_id: CollectionId,1108 keys: Option<Vec<PropertyKey>>,1109 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1110 let permissions = Self::property_permissions(collection_id);11111112 let key_permissions = keys1113 .map(|keys| {1114 keys.into_iter()1115 .filter_map(|key| {1116 permissions1117 .get(&key)1118 .map(|permission| PropertyKeyPermission {1119 key,1120 permission: permission.clone(),1121 })1122 })1123 .collect()1124 })1125 .unwrap_or_else(|| {1126 permissions1127 .into_iter()1128 .map(|(key, permission)| PropertyKeyPermission { key, permission })1129 .collect()1130 });11311132 Ok(key_permissions)1133 }11341135 pub fn toggle_allowlist(1136 collection: &CollectionHandle<T>,1137 sender: &T::CrossAccountId,1138 user: &T::CrossAccountId,1139 allowed: bool,1140 ) -> DispatchResult {1141 collection.check_is_owner_or_admin(sender)?;11421143 11441145 if allowed {1146 <Allowlist<T>>::insert((collection.id, user), true);1147 } else {1148 <Allowlist<T>>::remove((collection.id, user));1149 }11501151 Ok(())1152 }11531154 pub fn toggle_admin(1155 collection: &CollectionHandle<T>,1156 sender: &T::CrossAccountId,1157 user: &T::CrossAccountId,1158 admin: bool,1159 ) -> DispatchResult {1160 collection.check_is_owner(sender)?;11611162 let was_admin = <IsAdmin<T>>::get((collection.id, user));1163 if was_admin == admin {1164 return Ok(());1165 }1166 let amount = <AdminAmount<T>>::get(collection.id);11671168 if admin {1169 let amount = amount1170 .checked_add(1)1171 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1172 ensure!(1173 amount <= Self::collection_admins_limit(),1174 <Error<T>>::CollectionAdminCountExceeded,1175 );11761177 11781179 <AdminAmount<T>>::insert(collection.id, amount);1180 <IsAdmin<T>>::insert((collection.id, user), true);1181 } else {1182 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1183 <IsAdmin<T>>::remove((collection.id, user));1184 }11851186 Ok(())1187 }11881189 pub fn clamp_limits(1190 mode: CollectionMode,1191 old_limit: &CollectionLimits,1192 mut new_limit: CollectionLimits,1193 ) -> Result<CollectionLimits, DispatchError> {1194 let limits = old_limit;1195 limit_default!(old_limit, new_limit,1196 account_token_ownership_limit => ensure!(1197 new_limit <= MAX_TOKEN_OWNERSHIP,1198 <Error<T>>::CollectionLimitBoundsExceeded,1199 ),1200 sponsored_data_size => ensure!(1201 new_limit <= CUSTOM_DATA_LIMIT,1202 <Error<T>>::CollectionLimitBoundsExceeded,1203 ),12041205 sponsored_data_rate_limit => {},1206 token_limit => ensure!(1207 old_limit >= new_limit && new_limit > 0,1208 <Error<T>>::CollectionTokenLimitExceeded1209 ),12101211 sponsor_transfer_timeout(match mode {1212 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1213 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1214 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1215 }) => ensure!(1216 new_limit <= MAX_SPONSOR_TIMEOUT,1217 <Error<T>>::CollectionLimitBoundsExceeded,1218 ),1219 sponsor_approve_timeout => {},1220 owner_can_transfer => ensure!(1221 !limits.owner_can_transfer_instaled() ||1222 old_limit || !new_limit,1223 <Error<T>>::OwnerPermissionsCantBeReverted,1224 ),1225 owner_can_destroy => ensure!(1226 old_limit || !new_limit,1227 <Error<T>>::OwnerPermissionsCantBeReverted,1228 ),1229 transfers_enabled => {},1230 );1231 Ok(new_limit)1232 }12331234 pub fn clamp_permissions(1235 _mode: CollectionMode,1236 old_limit: &CollectionPermissions,1237 mut new_limit: CollectionPermissions,1238 ) -> Result<CollectionPermissions, DispatchError> {1239 limit_default_clone!(old_limit, new_limit,1240 access => {},1241 mint_mode => {},1242 nesting => { },1243 );1244 Ok(new_limit)1245 }1246}12471248#[macro_export]1249macro_rules! unsupported {1250 () => {1251 Err(<Error<T>>::UnsupportedOperation.into())1252 };1253}125412551256pub trait CommonWeightInfo<CrossAccountId> {1257 fn create_item() -> Weight;1258 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1259 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1260 fn burn_item() -> Weight;1261 fn set_collection_properties(amount: u32) -> Weight;1262 fn delete_collection_properties(amount: u32) -> Weight;1263 fn set_token_properties(amount: u32) -> Weight;1264 fn delete_token_properties(amount: u32) -> Weight;1265 fn set_token_property_permissions(amount: u32) -> Weight;1266 fn transfer() -> Weight;1267 fn approve() -> Weight;1268 fn transfer_from() -> Weight;1269 fn burn_from() -> Weight;12701271 1272 1273 1274 1275 fn burn_recursively_self_raw() -> Weight;1276 1277 1278 1279 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12801281 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1282 Self::burn_recursively_self_raw()1283 .saturating_mul(max_selfs.max(1) as u64)1284 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1285 }1286}12871288pub trait RefungibleExtensionsWeightInfo {1289 fn repartition() -> Weight;1290}12911292pub trait CommonCollectionOperations<T: Config> {1293 fn create_item(1294 &self,1295 sender: T::CrossAccountId,1296 to: T::CrossAccountId,1297 data: CreateItemData,1298 nesting_budget: &dyn Budget,1299 ) -> DispatchResultWithPostInfo;1300 fn create_multiple_items(1301 &self,1302 sender: T::CrossAccountId,1303 to: T::CrossAccountId,1304 data: Vec<CreateItemData>,1305 nesting_budget: &dyn Budget,1306 ) -> DispatchResultWithPostInfo;1307 fn create_multiple_items_ex(1308 &self,1309 sender: T::CrossAccountId,1310 data: CreateItemExData<T::CrossAccountId>,1311 nesting_budget: &dyn Budget,1312 ) -> DispatchResultWithPostInfo;1313 fn burn_item(1314 &self,1315 sender: T::CrossAccountId,1316 token: TokenId,1317 amount: u128,1318 ) -> DispatchResultWithPostInfo;1319 fn burn_item_recursively(1320 &self,1321 sender: T::CrossAccountId,1322 token: TokenId,1323 self_budget: &dyn Budget,1324 breadth_budget: &dyn Budget,1325 ) -> DispatchResultWithPostInfo;1326 fn set_collection_properties(1327 &self,1328 sender: T::CrossAccountId,1329 properties: Vec<Property>,1330 ) -> DispatchResultWithPostInfo;1331 fn delete_collection_properties(1332 &self,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResultWithPostInfo;1336 fn set_token_properties(1337 &self,1338 sender: T::CrossAccountId,1339 token_id: TokenId,1340 property: Vec<Property>,1341 nesting_budget: &dyn Budget,1342 ) -> DispatchResultWithPostInfo;1343 fn delete_token_properties(1344 &self,1345 sender: T::CrossAccountId,1346 token_id: TokenId,1347 property_keys: Vec<PropertyKey>,1348 nesting_budget: &dyn Budget,1349 ) -> DispatchResultWithPostInfo;1350 fn set_token_property_permissions(1351 &self,1352 sender: &T::CrossAccountId,1353 property_permissions: Vec<PropertyKeyPermission>,1354 ) -> DispatchResultWithPostInfo;1355 fn transfer(1356 &self,1357 sender: T::CrossAccountId,1358 to: T::CrossAccountId,1359 token: TokenId,1360 amount: u128,1361 nesting_budget: &dyn Budget,1362 ) -> DispatchResultWithPostInfo;1363 fn approve(1364 &self,1365 sender: T::CrossAccountId,1366 spender: T::CrossAccountId,1367 token: TokenId,1368 amount: u128,1369 ) -> DispatchResultWithPostInfo;1370 fn transfer_from(1371 &self,1372 sender: T::CrossAccountId,1373 from: T::CrossAccountId,1374 to: T::CrossAccountId,1375 token: TokenId,1376 amount: u128,1377 nesting_budget: &dyn Budget,1378 ) -> DispatchResultWithPostInfo;1379 fn burn_from(1380 &self,1381 sender: T::CrossAccountId,1382 from: T::CrossAccountId,1383 token: TokenId,1384 amount: u128,1385 nesting_budget: &dyn Budget,1386 ) -> DispatchResultWithPostInfo;13871388 fn check_nesting(1389 &self,1390 sender: T::CrossAccountId,1391 from: (CollectionId, TokenId),1392 under: TokenId,1393 nesting_budget: &dyn Budget,1394 ) -> DispatchResult;13951396 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13971398 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13991400 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1401 fn collection_tokens(&self) -> Vec<TokenId>;1402 fn token_exists(&self, token: TokenId) -> bool;1403 fn last_token_id(&self) -> TokenId;14041405 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1406 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1407 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1408 1409 fn total_supply(&self) -> u32;1410 1411 fn account_balance(&self, account: T::CrossAccountId) -> u32;1412 1413 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1414 1415 fn total_pieces(&self, token: TokenId) -> Option<u128>;1416 fn allowance(1417 &self,1418 sender: T::CrossAccountId,1419 spender: T::CrossAccountId,1420 token: TokenId,1421 ) -> u128;1422 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1423}14241425pub trait RefungibleExtensions<T>1426where1427 T: Config,1428{1429 fn repartition(1430 &self,1431 owner: &T::CrossAccountId,1432 token: TokenId,1433 amount: u128,1434 ) -> DispatchResultWithPostInfo;1435}143614371438pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1439 let post_info = PostDispatchInfo {1440 actual_weight: Some(weight),1441 pays_fee: Pays::Yes,1442 };1443 match res {1444 Ok(()) => Ok(post_info),1445 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1446 }1447}14481449impl<T: Config> From<PropertiesError> for Error<T> {1450 fn from(error: PropertiesError) -> Self {1451 match error {1452 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1453 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1454 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1455 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1456 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1457 }1458 }1459}