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 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 UserIsNotAllowedToNest,435 436 SourceCollectionIsNotAllowedToNest,437438 439 CollectionFieldSizeExceeded,440441 442 NoSpaceForProperty,443444 445 PropertyLimitReached,446447 448 PropertyKeyIsTooLong,449450 451 InvalidCharacterInPropertyKey,452453 454 EmptyPropertyKey,455456 457 CollectionIsExternal,458459 460 CollectionIsInternal,461 }462463 #[pallet::storage]464 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;465 #[pallet::storage]466 pub type DestroyedCollectionCount<T> =467 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;468469 470 #[pallet::storage]471 pub type CollectionById<T> = StorageMap<472 Hasher = Blake2_128Concat,473 Key = CollectionId,474 Value = Collection<<T as frame_system::Config>::AccountId>,475 QueryKind = OptionQuery,476 >;477478 479 #[pallet::storage]480 #[pallet::getter(fn collection_properties)]481 pub type CollectionProperties<T> = StorageMap<482 Hasher = Blake2_128Concat,483 Key = CollectionId,484 Value = Properties,485 QueryKind = ValueQuery,486 OnEmpty = up_data_structs::CollectionProperties,487 >;488489 #[pallet::storage]490 #[pallet::getter(fn property_permissions)]491 pub type CollectionPropertyPermissions<T> = StorageMap<492 Hasher = Blake2_128Concat,493 Key = CollectionId,494 Value = PropertiesPermissionMap,495 QueryKind = ValueQuery,496 >;497498 #[pallet::storage]499 pub type AdminAmount<T> = StorageMap<500 Hasher = Blake2_128Concat,501 Key = CollectionId,502 Value = u32,503 QueryKind = ValueQuery,504 >;505506 507 #[pallet::storage]508 pub type IsAdmin<T: Config> = StorageNMap<509 Key = (510 Key<Blake2_128Concat, CollectionId>,511 Key<Blake2_128Concat, T::CrossAccountId>,512 ),513 Value = bool,514 QueryKind = ValueQuery,515 >;516517 518 #[pallet::storage]519 pub type Allowlist<T: Config> = StorageNMap<520 Key = (521 Key<Blake2_128Concat, CollectionId>,522 Key<Blake2_128Concat, T::CrossAccountId>,523 ),524 Value = bool,525 QueryKind = ValueQuery,526 >;527528 529 #[pallet::storage]530 pub type DummyStorageValue<T: Config> = StorageValue<531 Value = (532 CollectionStats,533 CollectionId,534 TokenId,535 TokenChild,536 PhantomType<(537 TokenData<T::CrossAccountId>,538 RpcCollection<T::AccountId>,539 540 RmrkCollectionInfo<T::AccountId>,541 RmrkInstanceInfo<T::AccountId>,542 RmrkResourceInfo,543 RmrkPropertyInfo,544 RmrkBaseInfo<T::AccountId>,545 RmrkPartType,546 RmrkBoundedTheme,547 RmrkNftChild,548 )>,549 ),550 QueryKind = OptionQuery,551 >;552553 #[pallet::hooks]554 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {555 fn on_runtime_upgrade() -> Weight {556 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {557 use up_data_structs::{CollectionVersion1, CollectionVersion2};558 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {559 let mut props = Vec::new();560 if !v.offchain_schema.is_empty() {561 props.push(Property {562 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),563 value: v564 .offchain_schema565 .clone()566 .into_inner()567 .try_into()568 .expect("offchain schema too big"),569 });570 }571 if !v.variable_on_chain_schema.is_empty() {572 props.push(Property {573 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),574 value: v575 .variable_on_chain_schema576 .clone()577 .into_inner()578 .try_into()579 .expect("offchain schema too big"),580 });581 }582 if !v.const_on_chain_schema.is_empty() {583 props.push(Property {584 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),585 value: v586 .const_on_chain_schema587 .clone()588 .into_inner()589 .try_into()590 .expect("offchain schema too big"),591 });592 }593 props.push(Property {594 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),595 value: match v.schema_version {596 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),597 SchemaVersion::Unique => b"Unique".as_slice(),598 }599 .to_vec()600 .try_into()601 .unwrap(),602 });603 Self::set_scoped_collection_properties(604 id,605 PropertyScope::None,606 props.into_iter(),607 )608 .expect("existing data larger than properties");609 let mut new = CollectionVersion2::from(v.clone());610 new.permissions.access = Some(v.access);611 new.permissions.mint_mode = Some(v.mint_mode);612 Some(new)613 });614 }615616 0617 }618 }619}620621impl<T: Config> Pallet<T> {622 623 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {624 ensure!(625 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,626 <Error<T>>::AddressIsZero627 );628 Ok(())629 }630 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {631 <IsAdmin<T>>::iter_prefix((collection,))632 .map(|(a, _)| a)633 .collect()634 }635 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {636 <Allowlist<T>>::iter_prefix((collection,))637 .map(|(a, _)| a)638 .collect()639 }640 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {641 <Allowlist<T>>::get((collection, user))642 }643 pub fn collection_stats() -> CollectionStats {644 let created = <CreatedCollectionCount<T>>::get();645 let destroyed = <DestroyedCollectionCount<T>>::get();646 CollectionStats {647 created: created.0,648 destroyed: destroyed.0,649 alive: created.0 - destroyed.0,650 }651 }652653 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {654 let collection = <CollectionById<T>>::get(collection);655 if collection.is_none() {656 return None;657 }658659 let collection = collection.unwrap();660 let limits = collection.limits;661 let effective_limits = CollectionLimits {662 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),663 sponsored_data_size: Some(limits.sponsored_data_size()),664 sponsored_data_rate_limit: Some(665 limits666 .sponsored_data_rate_limit667 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),668 ),669 token_limit: Some(limits.token_limit()),670 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(671 match collection.mode {672 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,673 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,674 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,675 },676 )),677 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),678 owner_can_transfer: Some(limits.owner_can_transfer()),679 owner_can_destroy: Some(limits.owner_can_destroy()),680 transfers_enabled: Some(limits.transfers_enabled()),681 };682683 Some(effective_limits)684 }685686 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {687 let Collection {688 name,689 description,690 owner,691 mode,692 token_prefix,693 sponsorship,694 limits,695 permissions,696 external_collection,697 } = <CollectionById<T>>::get(collection)?;698699 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)700 .into_iter()701 .map(|(key, permission)| PropertyKeyPermission { key, permission })702 .collect();703704 let properties = <CollectionProperties<T>>::get(collection)705 .into_iter()706 .map(|(key, value)| Property { key, value })707 .collect();708709 let permissions = CollectionPermissions {710 access: Some(permissions.access()),711 mint_mode: Some(permissions.mint_mode()),712 nesting: Some(permissions.nesting().clone()),713 };714715 Some(RpcCollection {716 name: name.into_inner(),717 description: description.into_inner(),718 owner,719 mode,720 token_prefix: token_prefix.into_inner(),721 sponsorship,722 limits,723 permissions,724 token_property_permissions,725 properties,726 read_only: external_collection,727 })728 }729}730731macro_rules! limit_default {732 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{733 $(734 if let Some($new) = $new.$field {735 let $old = $old.$field($($arg)?);736 let _ = $new;737 let _ = $old;738 $check739 } else {740 $new.$field = $old.$field741 }742 )*743 }};744}745macro_rules! limit_default_clone {746 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{747 $(748 if let Some($new) = $new.$field.clone() {749 let $old = $old.$field($($arg)?);750 let _ = $new;751 let _ = $old;752 $check753 } else {754 $new.$field = $old.$field.clone()755 }756 )*757 }};758}759760impl<T: Config> Pallet<T> {761 pub fn init_collection(762 owner: T::CrossAccountId,763 data: CreateCollectionData<T::AccountId>,764 is_external: bool,765 ) -> Result<CollectionId, DispatchError> {766 {767 ensure!(768 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,769 Error::<T>::CollectionTokenPrefixLimitExceeded770 );771 }772773 let created_count = <CreatedCollectionCount<T>>::get()774 .0775 .checked_add(1)776 .ok_or(ArithmeticError::Overflow)?;777 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;778 let id = CollectionId(created_count);779780 781 ensure!(782 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,783 <Error<T>>::TotalCollectionsLimitExceeded784 );785786 787788 let collection = Collection {789 owner: owner.as_sub().clone(),790 name: data.name,791 mode: data.mode.clone(),792 description: data.description,793 token_prefix: data.token_prefix,794 sponsorship: data795 .pending_sponsor796 .map(SponsorshipState::Unconfirmed)797 .unwrap_or_default(),798 limits: data799 .limits800 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))801 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,802 permissions: data803 .permissions804 .map(|permissions| {805 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)806 })807 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,808 external_collection: is_external,809 };810811 let mut collection_properties = up_data_structs::CollectionProperties::get();812 collection_properties813 .try_set_from_iter(data.properties.into_iter())814 .map_err(<Error<T>>::from)?;815816 CollectionProperties::<T>::insert(id, collection_properties);817818 let mut token_props_permissions = PropertiesPermissionMap::new();819 token_props_permissions820 .try_set_from_iter(data.token_property_permissions.into_iter())821 .map_err(<Error<T>>::from)?;822823 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);824825 826 {827 let mut imbalance =828 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();829 imbalance.subsume(830 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(831 &T::TreasuryAccountId::get(),832 T::CollectionCreationPrice::get(),833 ),834 );835 <T as Config>::Currency::settle(836 &owner.as_sub(),837 imbalance,838 WithdrawReasons::TRANSFER,839 ExistenceRequirement::KeepAlive,840 )841 .map_err(|_| Error::<T>::NotSufficientFounds)?;842 }843844 <CreatedCollectionCount<T>>::put(created_count);845 <Pallet<T>>::deposit_event(Event::CollectionCreated(846 id,847 data.mode.id(),848 owner.as_sub().clone(),849 ));850 <PalletEvm<T>>::deposit_log(851 erc::CollectionHelpersEvents::CollectionCreated {852 owner: *owner.as_eth(),853 collection_id: eth::collection_id_to_address(id),854 }855 .to_log(T::ContractAddress::get()),856 );857 <CollectionById<T>>::insert(id, collection);858 Ok(id)859 }860861 pub fn destroy_collection(862 collection: CollectionHandle<T>,863 sender: &T::CrossAccountId,864 ) -> DispatchResult {865 ensure!(866 collection.limits.owner_can_destroy(),867 <Error<T>>::NoPermission,868 );869 collection.check_is_owner(sender)?;870871 let destroyed_collections = <DestroyedCollectionCount<T>>::get()872 .0873 .checked_add(1)874 .ok_or(ArithmeticError::Overflow)?;875876 877878 <DestroyedCollectionCount<T>>::put(destroyed_collections);879 <CollectionById<T>>::remove(collection.id);880 <AdminAmount<T>>::remove(collection.id);881 <IsAdmin<T>>::remove_prefix((collection.id,), None);882 <Allowlist<T>>::remove_prefix((collection.id,), None);883 <CollectionProperties<T>>::remove(collection.id);884885 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));886 Ok(())887 }888889 pub fn set_collection_property(890 collection: &CollectionHandle<T>,891 sender: &T::CrossAccountId,892 property: Property,893 ) -> DispatchResult {894 collection.check_is_owner_or_admin(sender)?;895896 CollectionProperties::<T>::try_mutate(collection.id, |properties| {897 let property = property.clone();898 properties.try_set(property.key, property.value)899 })900 .map_err(<Error<T>>::from)?;901902 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));903904 Ok(())905 }906907 pub fn set_scoped_collection_property(908 collection_id: CollectionId,909 scope: PropertyScope,910 property: Property,911 ) -> DispatchResult {912 CollectionProperties::<T>::try_mutate(collection_id, |properties| {913 properties.try_scoped_set(scope, property.key, property.value)914 })915 .map_err(<Error<T>>::from)?;916917 Ok(())918 }919920 pub fn set_scoped_collection_properties(921 collection_id: CollectionId,922 scope: PropertyScope,923 properties: impl Iterator<Item = Property>,924 ) -> DispatchResult {925 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {926 stored_properties.try_scoped_set_from_iter(scope, properties)927 })928 .map_err(<Error<T>>::from)?;929930 Ok(())931 }932933 #[transactional]934 pub fn set_collection_properties(935 collection: &CollectionHandle<T>,936 sender: &T::CrossAccountId,937 properties: Vec<Property>,938 ) -> DispatchResult {939 for property in properties {940 Self::set_collection_property(collection, sender, property)?;941 }942943 Ok(())944 }945946 pub fn delete_collection_property(947 collection: &CollectionHandle<T>,948 sender: &T::CrossAccountId,949 property_key: PropertyKey,950 ) -> DispatchResult {951 collection.check_is_owner_or_admin(sender)?;952953 CollectionProperties::<T>::try_mutate(collection.id, |properties| {954 properties.remove(&property_key)955 })956 .map_err(<Error<T>>::from)?;957958 Self::deposit_event(Event::CollectionPropertyDeleted(959 collection.id,960 property_key,961 ));962963 Ok(())964 }965966 #[transactional]967 pub fn delete_collection_properties(968 collection: &CollectionHandle<T>,969 sender: &T::CrossAccountId,970 property_keys: Vec<PropertyKey>,971 ) -> DispatchResult {972 for key in property_keys {973 Self::delete_collection_property(collection, sender, key)?;974 }975976 Ok(())977 }978979 980 pub fn set_property_permission_unchecked(981 collection: CollectionId,982 property_permission: PropertyKeyPermission,983 ) -> DispatchResult {984 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {985 permissions.try_set(property_permission.key, property_permission.permission)986 })987 .map_err(<Error<T>>::from)?;988 Ok(())989 }990991 pub fn set_property_permission(992 collection: &CollectionHandle<T>,993 sender: &T::CrossAccountId,994 property_permission: PropertyKeyPermission,995 ) -> DispatchResult {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_token_property_permissions(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 property_permissions: Vec<PropertyKeyPermission>,1026 ) -> DispatchResult {1027 for prop_pemission in property_permissions {1028 Self::set_property_permission(collection, sender, prop_pemission)?;1029 }10301031 Ok(())1032 }10331034 pub fn get_collection_property(1035 collection_id: CollectionId,1036 key: &PropertyKey,1037 ) -> Option<PropertyValue> {1038 Self::collection_properties(collection_id).get(key).cloned()1039 }10401041 pub fn bytes_keys_to_property_keys(1042 keys: Vec<Vec<u8>>,1043 ) -> Result<Vec<PropertyKey>, DispatchError> {1044 keys.into_iter()1045 .map(|key| -> Result<PropertyKey, DispatchError> {1046 key.try_into()1047 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1048 })1049 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1050 }10511052 pub fn filter_collection_properties(1053 collection_id: CollectionId,1054 keys: Option<Vec<PropertyKey>>,1055 ) -> Result<Vec<Property>, DispatchError> {1056 let properties = Self::collection_properties(collection_id);10571058 let properties = keys1059 .map(|keys| {1060 keys.into_iter()1061 .filter_map(|key| {1062 properties.get(&key).map(|value| Property {1063 key,1064 value: value.clone(),1065 })1066 })1067 .collect()1068 })1069 .unwrap_or_else(|| {1070 properties1071 .into_iter()1072 .map(|(key, value)| Property { key, value })1073 .collect()1074 });10751076 Ok(properties)1077 }10781079 pub fn filter_property_permissions(1080 collection_id: CollectionId,1081 keys: Option<Vec<PropertyKey>>,1082 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1083 let permissions = Self::property_permissions(collection_id);10841085 let key_permissions = keys1086 .map(|keys| {1087 keys.into_iter()1088 .filter_map(|key| {1089 permissions1090 .get(&key)1091 .map(|permission| PropertyKeyPermission {1092 key,1093 permission: permission.clone(),1094 })1095 })1096 .collect()1097 })1098 .unwrap_or_else(|| {1099 permissions1100 .into_iter()1101 .map(|(key, permission)| PropertyKeyPermission { key, permission })1102 .collect()1103 });11041105 Ok(key_permissions)1106 }11071108 pub fn toggle_allowlist(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 user: &T::CrossAccountId,1112 allowed: bool,1113 ) -> DispatchResult {1114 collection.check_is_owner_or_admin(sender)?;11151116 11171118 if allowed {1119 <Allowlist<T>>::insert((collection.id, user), true);1120 } else {1121 <Allowlist<T>>::remove((collection.id, user));1122 }11231124 Ok(())1125 }11261127 pub fn toggle_admin(1128 collection: &CollectionHandle<T>,1129 sender: &T::CrossAccountId,1130 user: &T::CrossAccountId,1131 admin: bool,1132 ) -> DispatchResult {1133 collection.check_is_owner(sender)?;11341135 let was_admin = <IsAdmin<T>>::get((collection.id, user));1136 if was_admin == admin {1137 return Ok(());1138 }1139 let amount = <AdminAmount<T>>::get(collection.id);11401141 if admin {1142 let amount = amount1143 .checked_add(1)1144 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1145 ensure!(1146 amount <= Self::collection_admins_limit(),1147 <Error<T>>::CollectionAdminCountExceeded,1148 );11491150 11511152 <AdminAmount<T>>::insert(collection.id, amount);1153 <IsAdmin<T>>::insert((collection.id, user), true);1154 } else {1155 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1156 <IsAdmin<T>>::remove((collection.id, user));1157 }11581159 Ok(())1160 }11611162 pub fn clamp_limits(1163 mode: CollectionMode,1164 old_limit: &CollectionLimits,1165 mut new_limit: CollectionLimits,1166 ) -> Result<CollectionLimits, DispatchError> {1167 let limits = old_limit;1168 limit_default!(old_limit, new_limit,1169 account_token_ownership_limit => ensure!(1170 new_limit <= MAX_TOKEN_OWNERSHIP,1171 <Error<T>>::CollectionLimitBoundsExceeded,1172 ),1173 sponsored_data_size => ensure!(1174 new_limit <= CUSTOM_DATA_LIMIT,1175 <Error<T>>::CollectionLimitBoundsExceeded,1176 ),11771178 sponsored_data_rate_limit => {},1179 token_limit => ensure!(1180 old_limit >= new_limit && new_limit > 0,1181 <Error<T>>::CollectionTokenLimitExceeded1182 ),11831184 sponsor_transfer_timeout(match mode {1185 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1186 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1187 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188 }) => ensure!(1189 new_limit <= MAX_SPONSOR_TIMEOUT,1190 <Error<T>>::CollectionLimitBoundsExceeded,1191 ),1192 sponsor_approve_timeout => {},1193 owner_can_transfer => ensure!(1194 !limits.owner_can_transfer_instaled() ||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 #[cfg(not(feature = "runtime-benchmarks"))]1217 ensure!(1218 1219 old_limit.permissive || !new_limit.permissive,1220 <Error<T>>::NoPermission,1221 )1222 },1223 );1224 Ok(new_limit)1225 }1226}12271228#[macro_export]1229macro_rules! unsupported {1230 () => {1231 Err(<Error<T>>::UnsupportedOperation.into())1232 };1233}123412351236pub trait CommonWeightInfo<CrossAccountId> {1237 fn create_item() -> Weight;1238 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1239 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1240 fn burn_item() -> Weight;1241 fn set_collection_properties(amount: u32) -> Weight;1242 fn delete_collection_properties(amount: u32) -> Weight;1243 fn set_token_properties(amount: u32) -> Weight;1244 fn delete_token_properties(amount: u32) -> Weight;1245 fn set_token_property_permissions(amount: u32) -> Weight;1246 fn transfer() -> Weight;1247 fn approve() -> Weight;1248 fn transfer_from() -> Weight;1249 fn burn_from() -> Weight;12501251 1252 1253 1254 1255 fn burn_recursively_self_raw() -> Weight;1256 1257 1258 1259 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12601261 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1262 Self::burn_recursively_self_raw()1263 .saturating_mul(max_selfs.max(1) as u64)1264 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1265 }1266}12671268pub trait CommonCollectionOperations<T: Config> {1269 fn create_item(1270 &self,1271 sender: T::CrossAccountId,1272 to: T::CrossAccountId,1273 data: CreateItemData,1274 nesting_budget: &dyn Budget,1275 ) -> DispatchResultWithPostInfo;1276 fn create_multiple_items(1277 &self,1278 sender: T::CrossAccountId,1279 to: T::CrossAccountId,1280 data: Vec<CreateItemData>,1281 nesting_budget: &dyn Budget,1282 ) -> DispatchResultWithPostInfo;1283 fn create_multiple_items_ex(1284 &self,1285 sender: T::CrossAccountId,1286 data: CreateItemExData<T::CrossAccountId>,1287 nesting_budget: &dyn Budget,1288 ) -> DispatchResultWithPostInfo;1289 fn burn_item(1290 &self,1291 sender: T::CrossAccountId,1292 token: TokenId,1293 amount: u128,1294 ) -> DispatchResultWithPostInfo;1295 fn burn_item_recursively(1296 &self,1297 sender: T::CrossAccountId,1298 token: TokenId,1299 self_budget: &dyn Budget,1300 breadth_budget: &dyn Budget,1301 ) -> DispatchResultWithPostInfo;1302 fn set_collection_properties(1303 &self,1304 sender: T::CrossAccountId,1305 properties: Vec<Property>,1306 ) -> DispatchResultWithPostInfo;1307 fn delete_collection_properties(1308 &self,1309 sender: &T::CrossAccountId,1310 property_keys: Vec<PropertyKey>,1311 ) -> DispatchResultWithPostInfo;1312 fn set_token_properties(1313 &self,1314 sender: T::CrossAccountId,1315 token_id: TokenId,1316 property: Vec<Property>,1317 ) -> DispatchResultWithPostInfo;1318 fn delete_token_properties(1319 &self,1320 sender: T::CrossAccountId,1321 token_id: TokenId,1322 property_keys: Vec<PropertyKey>,1323 ) -> DispatchResultWithPostInfo;1324 fn set_token_property_permissions(1325 &self,1326 sender: &T::CrossAccountId,1327 property_permissions: Vec<PropertyKeyPermission>,1328 ) -> DispatchResultWithPostInfo;1329 fn transfer(1330 &self,1331 sender: T::CrossAccountId,1332 to: T::CrossAccountId,1333 token: TokenId,1334 amount: u128,1335 nesting_budget: &dyn Budget,1336 ) -> DispatchResultWithPostInfo;1337 fn approve(1338 &self,1339 sender: T::CrossAccountId,1340 spender: T::CrossAccountId,1341 token: TokenId,1342 amount: u128,1343 ) -> DispatchResultWithPostInfo;1344 fn transfer_from(1345 &self,1346 sender: T::CrossAccountId,1347 from: T::CrossAccountId,1348 to: T::CrossAccountId,1349 token: TokenId,1350 amount: u128,1351 nesting_budget: &dyn Budget,1352 ) -> DispatchResultWithPostInfo;1353 fn burn_from(1354 &self,1355 sender: T::CrossAccountId,1356 from: T::CrossAccountId,1357 token: TokenId,1358 amount: u128,1359 nesting_budget: &dyn Budget,1360 ) -> DispatchResultWithPostInfo;13611362 fn check_nesting(1363 &self,1364 sender: T::CrossAccountId,1365 from: (CollectionId, TokenId),1366 under: TokenId,1367 budget: &dyn Budget,1368 ) -> DispatchResult;13691370 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13711372 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13731374 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1375 fn collection_tokens(&self) -> Vec<TokenId>;1376 fn token_exists(&self, token: TokenId) -> bool;1377 fn last_token_id(&self) -> TokenId;13781379 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1380 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1381 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1382 1383 fn total_supply(&self) -> u32;1384 1385 fn account_balance(&self, account: T::CrossAccountId) -> u32;1386 1387 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1388 fn allowance(1389 &self,1390 sender: T::CrossAccountId,1391 spender: T::CrossAccountId,1392 token: TokenId,1393 ) -> u128;1394}139513961397pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1398 let post_info = PostDispatchInfo {1399 actual_weight: Some(weight),1400 pays_fee: Pays::Yes,1401 };1402 match res {1403 Ok(()) => Ok(post_info),1404 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1405 }1406}14071408impl<T: Config> From<PropertiesError> for Error<T> {1409 fn from(error: PropertiesError) -> Self {1410 match error {1411 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1412 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1413 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1414 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1415 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1416 }1417 }1418}