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 <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 295 296 CollectionCreated(CollectionId, u8, T::AccountId),297298 299 300 301 302 303 CollectionDestroyed(CollectionId),304305 306 307 308 309 310 311 312 313 314 315 316 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318 319 320 321 322 323 324 325 326 327 328 329 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331 332 333 334 335 336 337 338 339 340 341 342 Transfer(343 CollectionId,344 TokenId,345 T::CrossAccountId,346 T::CrossAccountId,347 u128,348 ),349350 351 352 353 354 355 356 357 358 359 Approved(360 CollectionId,361 TokenId,362 T::CrossAccountId,363 T::CrossAccountId,364 u128,365 ),366367 CollectionPropertySet(CollectionId, PropertyKey),368369 CollectionPropertyDeleted(CollectionId, PropertyKey),370371 TokenPropertySet(CollectionId, TokenId, PropertyKey),372373 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),374375 PropertyPermissionSet(CollectionId, PropertyKey),376 }377378 #[pallet::error]379 pub enum Error<T> {380 381 CollectionNotFound,382 383 MustBeTokenOwner,384 385 NoPermission,386 387 CantDestroyNotEmptyCollection,388 389 PublicMintingNotAllowed,390 391 AddressNotInAllowlist,392393 394 CollectionNameLimitExceeded,395 396 CollectionDescriptionLimitExceeded,397 398 CollectionTokenPrefixLimitExceeded,399 400 TotalCollectionsLimitExceeded,401 402 CollectionAdminCountExceeded,403 404 CollectionLimitBoundsExceeded,405 406 OwnerPermissionsCantBeReverted,407 408 TransferNotAllowed,409 410 AccountTokenLimitExceeded,411 412 CollectionTokenLimitExceeded,413 414 MetadataFlagFrozen,415416 417 TokenNotFound,418 419 TokenValueTooLow,420 421 ApprovedValueTooLow,422 423 CantApproveMoreThanOwned,424425 426 AddressIsZero,427 428 UnsupportedOperation,429430 431 NotSufficientFounds,432433 434 NestingIsDisabled,435 436 OnlyOwnerAllowedToNest,437 438 SourceCollectionIsNotAllowedToNest,439440 441 CollectionFieldSizeExceeded,442443 444 NoSpaceForProperty,445446 447 PropertyLimitReached,448449 450 PropertyKeyIsTooLong,451452 453 InvalidCharacterInPropertyKey,454455 456 EmptyPropertyKey,457458 459 CollectionIsExternal,460461 462 CollectionIsInternal,463 }464465 #[pallet::storage]466 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;467 #[pallet::storage]468 pub type DestroyedCollectionCount<T> =469 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;470471 472 #[pallet::storage]473 pub type CollectionById<T> = StorageMap<474 Hasher = Blake2_128Concat,475 Key = CollectionId,476 Value = Collection<<T as frame_system::Config>::AccountId>,477 QueryKind = OptionQuery,478 >;479480 481 #[pallet::storage]482 #[pallet::getter(fn collection_properties)]483 pub type CollectionProperties<T> = StorageMap<484 Hasher = Blake2_128Concat,485 Key = CollectionId,486 Value = Properties,487 QueryKind = ValueQuery,488 OnEmpty = up_data_structs::CollectionProperties,489 >;490491 #[pallet::storage]492 #[pallet::getter(fn property_permissions)]493 pub type CollectionPropertyPermissions<T> = StorageMap<494 Hasher = Blake2_128Concat,495 Key = CollectionId,496 Value = PropertiesPermissionMap,497 QueryKind = ValueQuery,498 >;499500 #[pallet::storage]501 pub type AdminAmount<T> = StorageMap<502 Hasher = Blake2_128Concat,503 Key = CollectionId,504 Value = u32,505 QueryKind = ValueQuery,506 >;507508 509 #[pallet::storage]510 pub type IsAdmin<T: Config> = StorageNMap<511 Key = (512 Key<Blake2_128Concat, CollectionId>,513 Key<Blake2_128Concat, T::CrossAccountId>,514 ),515 Value = bool,516 QueryKind = ValueQuery,517 >;518519 520 #[pallet::storage]521 pub type Allowlist<T: Config> = StorageNMap<522 Key = (523 Key<Blake2_128Concat, CollectionId>,524 Key<Blake2_128Concat, T::CrossAccountId>,525 ),526 Value = bool,527 QueryKind = ValueQuery,528 >;529530 531 #[pallet::storage]532 pub type DummyStorageValue<T: Config> = StorageValue<533 Value = (534 CollectionStats,535 CollectionId,536 TokenId,537 TokenChild,538 PhantomType<(539 TokenData<T::CrossAccountId>,540 RpcCollection<T::AccountId>,541 542 RmrkCollectionInfo<T::AccountId>,543 RmrkInstanceInfo<T::AccountId>,544 RmrkResourceInfo,545 RmrkPropertyInfo,546 RmrkBaseInfo<T::AccountId>,547 RmrkPartType,548 RmrkTheme,549 RmrkNftChild,550 )>,551 ),552 QueryKind = OptionQuery,553 >;554555 #[pallet::hooks]556 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {557 fn on_runtime_upgrade() -> Weight {558 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {559 use up_data_structs::{CollectionVersion1, CollectionVersion2};560 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {561 let mut props = Vec::new();562 if !v.offchain_schema.is_empty() {563 props.push(Property {564 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),565 value: v566 .offchain_schema567 .clone()568 .into_inner()569 .try_into()570 .expect("offchain schema too big"),571 });572 }573 if !v.variable_on_chain_schema.is_empty() {574 props.push(Property {575 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),576 value: v577 .variable_on_chain_schema578 .clone()579 .into_inner()580 .try_into()581 .expect("offchain schema too big"),582 });583 }584 if !v.const_on_chain_schema.is_empty() {585 props.push(Property {586 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),587 value: v588 .const_on_chain_schema589 .clone()590 .into_inner()591 .try_into()592 .expect("offchain schema too big"),593 });594 }595 props.push(Property {596 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),597 value: match v.schema_version {598 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),599 SchemaVersion::Unique => b"Unique".as_slice(),600 }601 .to_vec()602 .try_into()603 .unwrap(),604 });605 Self::set_scoped_collection_properties(606 id,607 PropertyScope::None,608 props.into_iter(),609 )610 .expect("existing data larger than properties");611 let mut new = CollectionVersion2::from(v.clone());612 new.permissions.access = Some(v.access);613 new.permissions.mint_mode = Some(v.mint_mode);614 Some(new)615 });616 }617618 0619 }620 }621}622623impl<T: Config> Pallet<T> {624 625 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {626 ensure!(627 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,628 <Error<T>>::AddressIsZero629 );630 Ok(())631 }632 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {633 <IsAdmin<T>>::iter_prefix((collection,))634 .map(|(a, _)| a)635 .collect()636 }637 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {638 <Allowlist<T>>::iter_prefix((collection,))639 .map(|(a, _)| a)640 .collect()641 }642 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {643 <Allowlist<T>>::get((collection, user))644 }645 pub fn collection_stats() -> CollectionStats {646 let created = <CreatedCollectionCount<T>>::get();647 let destroyed = <DestroyedCollectionCount<T>>::get();648 CollectionStats {649 created: created.0,650 destroyed: destroyed.0,651 alive: created.0 - destroyed.0,652 }653 }654655 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {656 let collection = <CollectionById<T>>::get(collection);657 if collection.is_none() {658 return None;659 }660661 let collection = collection.unwrap();662 let limits = collection.limits;663 let effective_limits = CollectionLimits {664 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),665 sponsored_data_size: Some(limits.sponsored_data_size()),666 sponsored_data_rate_limit: Some(667 limits668 .sponsored_data_rate_limit669 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),670 ),671 token_limit: Some(limits.token_limit()),672 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(673 match collection.mode {674 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,675 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,676 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,677 },678 )),679 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),680 owner_can_transfer: Some(limits.owner_can_transfer()),681 owner_can_destroy: Some(limits.owner_can_destroy()),682 transfers_enabled: Some(limits.transfers_enabled()),683 };684685 Some(effective_limits)686 }687688 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {689 let Collection {690 name,691 description,692 owner,693 mode,694 token_prefix,695 sponsorship,696 limits,697 permissions,698 external_collection,699 } = <CollectionById<T>>::get(collection)?;700701 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)702 .into_iter()703 .map(|(key, permission)| PropertyKeyPermission { key, permission })704 .collect();705706 let properties = <CollectionProperties<T>>::get(collection)707 .into_iter()708 .map(|(key, value)| Property { key, value })709 .collect();710711 let permissions = CollectionPermissions {712 access: Some(permissions.access()),713 mint_mode: Some(permissions.mint_mode()),714 nesting: Some(permissions.nesting().clone()),715 };716717 Some(RpcCollection {718 name: name.into_inner(),719 description: description.into_inner(),720 owner,721 mode,722 token_prefix: token_prefix.into_inner(),723 sponsorship,724 limits,725 permissions,726 token_property_permissions,727 properties,728 read_only: external_collection,729 })730 }731}732733macro_rules! limit_default {734 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{735 $(736 if let Some($new) = $new.$field {737 let $old = $old.$field($($arg)?);738 let _ = $new;739 let _ = $old;740 $check741 } else {742 $new.$field = $old.$field743 }744 )*745 }};746}747macro_rules! limit_default_clone {748 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{749 $(750 if let Some($new) = $new.$field.clone() {751 let $old = $old.$field($($arg)?);752 let _ = $new;753 let _ = $old;754 $check755 } else {756 $new.$field = $old.$field.clone()757 }758 )*759 }};760}761762impl<T: Config> Pallet<T> {763 pub fn init_collection(764 owner: T::CrossAccountId,765 data: CreateCollectionData<T::AccountId>,766 is_external: bool,767 ) -> Result<CollectionId, DispatchError> {768 {769 ensure!(770 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,771 Error::<T>::CollectionTokenPrefixLimitExceeded772 );773 }774775 let created_count = <CreatedCollectionCount<T>>::get()776 .0777 .checked_add(1)778 .ok_or(ArithmeticError::Overflow)?;779 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;780 let id = CollectionId(created_count);781782 783 ensure!(784 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,785 <Error<T>>::TotalCollectionsLimitExceeded786 );787788 789790 let collection = Collection {791 owner: owner.as_sub().clone(),792 name: data.name,793 mode: data.mode.clone(),794 description: data.description,795 token_prefix: data.token_prefix,796 sponsorship: data797 .pending_sponsor798 .map(SponsorshipState::Unconfirmed)799 .unwrap_or_default(),800 limits: data801 .limits802 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))803 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,804 permissions: data805 .permissions806 .map(|permissions| {807 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)808 })809 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,810 external_collection: is_external,811 };812813 let mut collection_properties = up_data_structs::CollectionProperties::get();814 collection_properties815 .try_set_from_iter(data.properties.into_iter())816 .map_err(<Error<T>>::from)?;817818 CollectionProperties::<T>::insert(id, collection_properties);819820 let mut token_props_permissions = PropertiesPermissionMap::new();821 token_props_permissions822 .try_set_from_iter(data.token_property_permissions.into_iter())823 .map_err(<Error<T>>::from)?;824825 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);826827 828 {829 let mut imbalance =830 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();831 imbalance.subsume(832 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(833 &T::TreasuryAccountId::get(),834 T::CollectionCreationPrice::get(),835 ),836 );837 <T as Config>::Currency::settle(838 &owner.as_sub(),839 imbalance,840 WithdrawReasons::TRANSFER,841 ExistenceRequirement::KeepAlive,842 )843 .map_err(|_| Error::<T>::NotSufficientFounds)?;844 }845846 <CreatedCollectionCount<T>>::put(created_count);847 <Pallet<T>>::deposit_event(Event::CollectionCreated(848 id,849 data.mode.id(),850 owner.as_sub().clone(),851 ));852 <PalletEvm<T>>::deposit_log(853 erc::CollectionHelpersEvents::CollectionCreated {854 owner: *owner.as_eth(),855 collection_id: eth::collection_id_to_address(id),856 }857 .to_log(T::ContractAddress::get()),858 );859 <CollectionById<T>>::insert(id, collection);860 Ok(id)861 }862863 pub fn destroy_collection(864 collection: CollectionHandle<T>,865 sender: &T::CrossAccountId,866 ) -> DispatchResult {867 ensure!(868 collection.limits.owner_can_destroy(),869 <Error<T>>::NoPermission,870 );871 collection.check_is_owner(sender)?;872873 let destroyed_collections = <DestroyedCollectionCount<T>>::get()874 .0875 .checked_add(1)876 .ok_or(ArithmeticError::Overflow)?;877878 879880 <DestroyedCollectionCount<T>>::put(destroyed_collections);881 <CollectionById<T>>::remove(collection.id);882 <AdminAmount<T>>::remove(collection.id);883 <IsAdmin<T>>::remove_prefix((collection.id,), None);884 <Allowlist<T>>::remove_prefix((collection.id,), None);885 <CollectionProperties<T>>::remove(collection.id);886887 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));888 Ok(())889 }890891 pub fn set_collection_property(892 collection: &CollectionHandle<T>,893 sender: &T::CrossAccountId,894 property: Property,895 ) -> DispatchResult {896 collection.check_is_owner_or_admin(sender)?;897898 CollectionProperties::<T>::try_mutate(collection.id, |properties| {899 let property = property.clone();900 properties.try_set(property.key, property.value)901 })902 .map_err(<Error<T>>::from)?;903904 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));905906 Ok(())907 }908909 pub fn set_scoped_collection_property(910 collection_id: CollectionId,911 scope: PropertyScope,912 property: Property,913 ) -> DispatchResult {914 CollectionProperties::<T>::try_mutate(collection_id, |properties| {915 properties.try_scoped_set(scope, property.key, property.value)916 })917 .map_err(<Error<T>>::from)?;918919 Ok(())920 }921922 pub fn set_scoped_collection_properties(923 collection_id: CollectionId,924 scope: PropertyScope,925 properties: impl Iterator<Item = Property>,926 ) -> DispatchResult {927 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {928 stored_properties.try_scoped_set_from_iter(scope, properties)929 })930 .map_err(<Error<T>>::from)?;931932 Ok(())933 }934935 #[transactional]936 pub fn set_collection_properties(937 collection: &CollectionHandle<T>,938 sender: &T::CrossAccountId,939 properties: Vec<Property>,940 ) -> DispatchResult {941 for property in properties {942 Self::set_collection_property(collection, sender, property)?;943 }944945 Ok(())946 }947948 pub fn delete_collection_property(949 collection: &CollectionHandle<T>,950 sender: &T::CrossAccountId,951 property_key: PropertyKey,952 ) -> DispatchResult {953 collection.check_is_owner_or_admin(sender)?;954955 CollectionProperties::<T>::try_mutate(collection.id, |properties| {956 properties.remove(&property_key)957 })958 .map_err(<Error<T>>::from)?;959960 Self::deposit_event(Event::CollectionPropertyDeleted(961 collection.id,962 property_key,963 ));964965 Ok(())966 }967968 #[transactional]969 pub fn delete_collection_properties(970 collection: &CollectionHandle<T>,971 sender: &T::CrossAccountId,972 property_keys: Vec<PropertyKey>,973 ) -> DispatchResult {974 for key in property_keys {975 Self::delete_collection_property(collection, sender, key)?;976 }977978 Ok(())979 }980981 982 pub fn set_property_permission_unchecked(983 collection: CollectionId,984 property_permission: PropertyKeyPermission,985 ) -> DispatchResult {986 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {987 permissions.try_set(property_permission.key, property_permission.permission)988 })989 .map_err(<Error<T>>::from)?;990 Ok(())991 }992993 pub fn set_property_permission(994 collection: &CollectionHandle<T>,995 sender: &T::CrossAccountId,996 property_permission: PropertyKeyPermission,997 ) -> DispatchResult {998 collection.check_is_owner_or_admin(sender)?;9991000 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1001 let current_permission = all_permissions.get(&property_permission.key);1002 if matches![1003 current_permission,1004 Some(PropertyPermission { mutable: false, .. })1005 ] {1006 return Err(<Error<T>>::NoPermission.into());1007 }10081009 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1010 let property_permission = property_permission.clone();1011 permissions.try_set(property_permission.key, property_permission.permission)1012 })1013 .map_err(<Error<T>>::from)?;10141015 Self::deposit_event(Event::PropertyPermissionSet(1016 collection.id,1017 property_permission.key,1018 ));10191020 Ok(())1021 }10221023 #[transactional]1024 pub fn set_property_permissions(1025 collection: &CollectionHandle<T>,1026 sender: &T::CrossAccountId,1027 property_permissions: Vec<PropertyKeyPermission>,1028 ) -> DispatchResult {1029 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_owner_or_admin(sender)?;11171118 11191120 if allowed {1121 <Allowlist<T>>::insert((collection.id, user), true);1122 } else {1123 <Allowlist<T>>::remove((collection.id, user));1124 }11251126 Ok(())1127 }11281129 pub fn toggle_admin(1130 collection: &CollectionHandle<T>,1131 sender: &T::CrossAccountId,1132 user: &T::CrossAccountId,1133 admin: bool,1134 ) -> DispatchResult {1135 collection.check_is_owner(sender)?;11361137 let was_admin = <IsAdmin<T>>::get((collection.id, user));1138 if was_admin == admin {1139 return Ok(());1140 }1141 let amount = <AdminAmount<T>>::get(collection.id);11421143 if admin {1144 let amount = amount1145 .checked_add(1)1146 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1147 ensure!(1148 amount <= Self::collection_admins_limit(),1149 <Error<T>>::CollectionAdminCountExceeded,1150 );11511152 11531154 <AdminAmount<T>>::insert(collection.id, amount);1155 <IsAdmin<T>>::insert((collection.id, user), true);1156 } else {1157 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1158 <IsAdmin<T>>::remove((collection.id, user));1159 }11601161 Ok(())1162 }11631164 pub fn clamp_limits(1165 mode: CollectionMode,1166 old_limit: &CollectionLimits,1167 mut new_limit: CollectionLimits,1168 ) -> Result<CollectionLimits, DispatchError> {1169 limit_default!(old_limit, new_limit,1170 account_token_ownership_limit => ensure!(1171 new_limit <= MAX_TOKEN_OWNERSHIP,1172 <Error<T>>::CollectionLimitBoundsExceeded,1173 ),1174 sponsored_data_size => ensure!(1175 new_limit <= CUSTOM_DATA_LIMIT,1176 <Error<T>>::CollectionLimitBoundsExceeded,1177 ),11781179 sponsored_data_rate_limit => {},1180 token_limit => ensure!(1181 old_limit >= new_limit && new_limit > 0,1182 <Error<T>>::CollectionTokenLimitExceeded1183 ),11841185 sponsor_transfer_timeout(match mode {1186 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1187 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1189 }) => ensure!(1190 new_limit <= MAX_SPONSOR_TIMEOUT,1191 <Error<T>>::CollectionLimitBoundsExceeded,1192 ),1193 sponsor_approve_timeout => {},1194 owner_can_transfer => ensure!(1195 old_limit || !new_limit,1196 <Error<T>>::OwnerPermissionsCantBeReverted,1197 ),1198 owner_can_destroy => ensure!(1199 old_limit || !new_limit,1200 <Error<T>>::OwnerPermissionsCantBeReverted,1201 ),1202 transfers_enabled => {},1203 );1204 Ok(new_limit)1205 }12061207 pub fn clamp_permissions(1208 _mode: CollectionMode,1209 old_limit: &CollectionPermissions,1210 mut new_limit: CollectionPermissions,1211 ) -> Result<CollectionPermissions, DispatchError> {1212 limit_default_clone!(old_limit, new_limit,1213 access => {},1214 mint_mode => {},1215 nesting => {},1216 );1217 Ok(new_limit)1218 }1219}12201221#[macro_export]1222macro_rules! unsupported {1223 () => {1224 Err(<Error<T>>::UnsupportedOperation.into())1225 };1226}122712281229pub trait CommonWeightInfo<CrossAccountId> {1230 fn create_item() -> Weight;1231 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1232 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1233 fn burn_item() -> Weight;1234 fn set_collection_properties(amount: u32) -> Weight;1235 fn delete_collection_properties(amount: u32) -> Weight;1236 fn set_token_properties(amount: u32) -> Weight;1237 fn delete_token_properties(amount: u32) -> Weight;1238 fn set_property_permissions(amount: u32) -> Weight;1239 fn transfer() -> Weight;1240 fn approve() -> Weight;1241 fn transfer_from() -> Weight;1242 fn burn_from() -> Weight;12431244 1245 1246 1247 1248 fn burn_recursively_self_raw() -> Weight;1249 1250 1251 1252 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12531254 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1255 Self::burn_recursively_self_raw()1256 .saturating_mul(max_selfs.max(1) as u64)1257 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1258 }1259}12601261pub trait CommonCollectionOperations<T: Config> {1262 fn create_item(1263 &self,1264 sender: T::CrossAccountId,1265 to: T::CrossAccountId,1266 data: CreateItemData,1267 nesting_budget: &dyn Budget,1268 ) -> DispatchResultWithPostInfo;1269 fn create_multiple_items(1270 &self,1271 sender: T::CrossAccountId,1272 to: T::CrossAccountId,1273 data: Vec<CreateItemData>,1274 nesting_budget: &dyn Budget,1275 ) -> DispatchResultWithPostInfo;1276 fn create_multiple_items_ex(1277 &self,1278 sender: T::CrossAccountId,1279 data: CreateItemExData<T::CrossAccountId>,1280 nesting_budget: &dyn Budget,1281 ) -> DispatchResultWithPostInfo;1282 fn burn_item(1283 &self,1284 sender: T::CrossAccountId,1285 token: TokenId,1286 amount: u128,1287 ) -> DispatchResultWithPostInfo;1288 fn burn_item_recursively(1289 &self,1290 sender: T::CrossAccountId,1291 token: TokenId,1292 self_budget: &dyn Budget,1293 breadth_budget: &dyn Budget,1294 ) -> DispatchResultWithPostInfo;1295 fn set_collection_properties(1296 &self,1297 sender: T::CrossAccountId,1298 properties: Vec<Property>,1299 ) -> DispatchResultWithPostInfo;1300 fn delete_collection_properties(1301 &self,1302 sender: &T::CrossAccountId,1303 property_keys: Vec<PropertyKey>,1304 ) -> DispatchResultWithPostInfo;1305 fn set_token_properties(1306 &self,1307 sender: T::CrossAccountId,1308 token_id: TokenId,1309 property: Vec<Property>,1310 ) -> DispatchResultWithPostInfo;1311 fn delete_token_properties(1312 &self,1313 sender: T::CrossAccountId,1314 token_id: TokenId,1315 property_keys: Vec<PropertyKey>,1316 ) -> DispatchResultWithPostInfo;1317 fn set_property_permissions(1318 &self,1319 sender: &T::CrossAccountId,1320 property_permissions: Vec<PropertyKeyPermission>,1321 ) -> DispatchResultWithPostInfo;1322 fn transfer(1323 &self,1324 sender: T::CrossAccountId,1325 to: T::CrossAccountId,1326 token: TokenId,1327 amount: u128,1328 nesting_budget: &dyn Budget,1329 ) -> DispatchResultWithPostInfo;1330 fn approve(1331 &self,1332 sender: T::CrossAccountId,1333 spender: T::CrossAccountId,1334 token: TokenId,1335 amount: u128,1336 ) -> DispatchResultWithPostInfo;1337 fn transfer_from(1338 &self,1339 sender: T::CrossAccountId,1340 from: T::CrossAccountId,1341 to: T::CrossAccountId,1342 token: TokenId,1343 amount: u128,1344 nesting_budget: &dyn Budget,1345 ) -> DispatchResultWithPostInfo;1346 fn burn_from(1347 &self,1348 sender: T::CrossAccountId,1349 from: T::CrossAccountId,1350 token: TokenId,1351 amount: u128,1352 nesting_budget: &dyn Budget,1353 ) -> DispatchResultWithPostInfo;13541355 fn check_nesting(1356 &self,1357 sender: T::CrossAccountId,1358 from: (CollectionId, TokenId),1359 under: TokenId,1360 budget: &dyn Budget,1361 ) -> DispatchResult;13621363 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13641365 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1368 fn collection_tokens(&self) -> Vec<TokenId>;1369 fn token_exists(&self, token: TokenId) -> bool;1370 fn last_token_id(&self) -> TokenId;13711372 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1373 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1374 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1375 1376 fn total_supply(&self) -> u32;1377 1378 fn account_balance(&self, account: T::CrossAccountId) -> u32;1379 1380 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1381 fn allowance(1382 &self,1383 sender: T::CrossAccountId,1384 spender: T::CrossAccountId,1385 token: TokenId,1386 ) -> u128;1387}138813891390pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1391 let post_info = PostDispatchInfo {1392 actual_weight: Some(weight),1393 pays_fee: Pays::Yes,1394 };1395 match res {1396 Ok(()) => Ok(post_info),1397 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1398 }1399}14001401impl<T: Config> From<PropertiesError> for Error<T> {1402 fn from(error: PropertiesError) -> Self {1403 match error {1404 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1405 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1406 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1407 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1408 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1409 }1410 }1411}