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 StorageVersion::new(1).put::<Pallet<T>>();557558 0559 }560 }561}562563impl<T: Config> Pallet<T> {564 565 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {566 ensure!(567 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,568 <Error<T>>::AddressIsZero569 );570 Ok(())571 }572 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {573 <IsAdmin<T>>::iter_prefix((collection,))574 .map(|(a, _)| a)575 .collect()576 }577 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {578 <Allowlist<T>>::iter_prefix((collection,))579 .map(|(a, _)| a)580 .collect()581 }582 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {583 <Allowlist<T>>::get((collection, user))584 }585 pub fn collection_stats() -> CollectionStats {586 let created = <CreatedCollectionCount<T>>::get();587 let destroyed = <DestroyedCollectionCount<T>>::get();588 CollectionStats {589 created: created.0,590 destroyed: destroyed.0,591 alive: created.0 - destroyed.0,592 }593 }594595 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {596 let collection = <CollectionById<T>>::get(collection);597 if collection.is_none() {598 return None;599 }600601 let collection = collection.unwrap();602 let limits = collection.limits;603 let effective_limits = CollectionLimits {604 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),605 sponsored_data_size: Some(limits.sponsored_data_size()),606 sponsored_data_rate_limit: Some(607 limits608 .sponsored_data_rate_limit609 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),610 ),611 token_limit: Some(limits.token_limit()),612 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(613 match collection.mode {614 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,615 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,616 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,617 },618 )),619 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),620 owner_can_transfer: Some(limits.owner_can_transfer()),621 owner_can_destroy: Some(limits.owner_can_destroy()),622 transfers_enabled: Some(limits.transfers_enabled()),623 };624625 Some(effective_limits)626 }627628 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {629 let Collection {630 name,631 description,632 owner,633 mode,634 token_prefix,635 sponsorship,636 limits,637 permissions,638 external_collection,639 } = <CollectionById<T>>::get(collection)?;640641 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)642 .into_iter()643 .map(|(key, permission)| PropertyKeyPermission { key, permission })644 .collect();645646 let properties = <CollectionProperties<T>>::get(collection)647 .into_iter()648 .map(|(key, value)| Property { key, value })649 .collect();650651 let permissions = CollectionPermissions {652 access: Some(permissions.access()),653 mint_mode: Some(permissions.mint_mode()),654 nesting: Some(permissions.nesting().clone()),655 };656657 Some(RpcCollection {658 name: name.into_inner(),659 description: description.into_inner(),660 owner,661 mode,662 token_prefix: token_prefix.into_inner(),663 sponsorship,664 limits,665 permissions,666 token_property_permissions,667 properties,668 read_only: external_collection,669 })670 }671}672673macro_rules! limit_default {674 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{675 $(676 if let Some($new) = $new.$field {677 let $old = $old.$field($($arg)?);678 let _ = $new;679 let _ = $old;680 $check681 } else {682 $new.$field = $old.$field683 }684 )*685 }};686}687macro_rules! limit_default_clone {688 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{689 $(690 if let Some($new) = $new.$field.clone() {691 let $old = $old.$field($($arg)?);692 let _ = $new;693 let _ = $old;694 $check695 } else {696 $new.$field = $old.$field.clone()697 }698 )*699 }};700}701702impl<T: Config> Pallet<T> {703 pub fn init_collection(704 owner: T::CrossAccountId,705 data: CreateCollectionData<T::AccountId>,706 is_external: bool,707 ) -> Result<CollectionId, DispatchError> {708 {709 ensure!(710 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,711 Error::<T>::CollectionTokenPrefixLimitExceeded712 );713 }714715 let created_count = <CreatedCollectionCount<T>>::get()716 .0717 .checked_add(1)718 .ok_or(ArithmeticError::Overflow)?;719 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;720 let id = CollectionId(created_count);721722 723 ensure!(724 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,725 <Error<T>>::TotalCollectionsLimitExceeded726 );727728 729730 let collection = Collection {731 owner: owner.as_sub().clone(),732 name: data.name,733 mode: data.mode.clone(),734 description: data.description,735 token_prefix: data.token_prefix,736 sponsorship: data737 .pending_sponsor738 .map(SponsorshipState::Unconfirmed)739 .unwrap_or_default(),740 limits: data741 .limits742 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))743 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,744 permissions: data745 .permissions746 .map(|permissions| {747 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)748 })749 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,750 external_collection: is_external,751 };752753 let mut collection_properties = up_data_structs::CollectionProperties::get();754 collection_properties755 .try_set_from_iter(data.properties.into_iter())756 .map_err(<Error<T>>::from)?;757758 CollectionProperties::<T>::insert(id, collection_properties);759760 let mut token_props_permissions = PropertiesPermissionMap::new();761 token_props_permissions762 .try_set_from_iter(data.token_property_permissions.into_iter())763 .map_err(<Error<T>>::from)?;764765 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);766767 768 {769 let mut imbalance =770 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();771 imbalance.subsume(772 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(773 &T::TreasuryAccountId::get(),774 T::CollectionCreationPrice::get(),775 ),776 );777 <T as Config>::Currency::settle(778 &owner.as_sub(),779 imbalance,780 WithdrawReasons::TRANSFER,781 ExistenceRequirement::KeepAlive,782 )783 .map_err(|_| Error::<T>::NotSufficientFounds)?;784 }785786 <CreatedCollectionCount<T>>::put(created_count);787 <Pallet<T>>::deposit_event(Event::CollectionCreated(788 id,789 data.mode.id(),790 owner.as_sub().clone(),791 ));792 <PalletEvm<T>>::deposit_log(793 erc::CollectionHelpersEvents::CollectionCreated {794 owner: *owner.as_eth(),795 collection_id: eth::collection_id_to_address(id),796 }797 .to_log(T::ContractAddress::get()),798 );799 <CollectionById<T>>::insert(id, collection);800 Ok(id)801 }802803 pub fn destroy_collection(804 collection: CollectionHandle<T>,805 sender: &T::CrossAccountId,806 ) -> DispatchResult {807 ensure!(808 collection.limits.owner_can_destroy(),809 <Error<T>>::NoPermission,810 );811 collection.check_is_owner(sender)?;812813 let destroyed_collections = <DestroyedCollectionCount<T>>::get()814 .0815 .checked_add(1)816 .ok_or(ArithmeticError::Overflow)?;817818 819820 <DestroyedCollectionCount<T>>::put(destroyed_collections);821 <CollectionById<T>>::remove(collection.id);822 <AdminAmount<T>>::remove(collection.id);823 <IsAdmin<T>>::remove_prefix((collection.id,), None);824 <Allowlist<T>>::remove_prefix((collection.id,), None);825 <CollectionProperties<T>>::remove(collection.id);826827 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));828 Ok(())829 }830831 pub fn set_collection_property(832 collection: &CollectionHandle<T>,833 sender: &T::CrossAccountId,834 property: Property,835 ) -> DispatchResult {836 collection.check_is_owner_or_admin(sender)?;837838 CollectionProperties::<T>::try_mutate(collection.id, |properties| {839 let property = property.clone();840 properties.try_set(property.key, property.value)841 })842 .map_err(<Error<T>>::from)?;843844 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));845846 Ok(())847 }848849 pub fn set_scoped_collection_property(850 collection_id: CollectionId,851 scope: PropertyScope,852 property: Property,853 ) -> DispatchResult {854 CollectionProperties::<T>::try_mutate(collection_id, |properties| {855 properties.try_scoped_set(scope, property.key, property.value)856 })857 .map_err(<Error<T>>::from)?;858859 Ok(())860 }861862 pub fn set_scoped_collection_properties(863 collection_id: CollectionId,864 scope: PropertyScope,865 properties: impl Iterator<Item = Property>,866 ) -> DispatchResult {867 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {868 stored_properties.try_scoped_set_from_iter(scope, properties)869 })870 .map_err(<Error<T>>::from)?;871872 Ok(())873 }874875 #[transactional]876 pub fn set_collection_properties(877 collection: &CollectionHandle<T>,878 sender: &T::CrossAccountId,879 properties: Vec<Property>,880 ) -> DispatchResult {881 for property in properties {882 Self::set_collection_property(collection, sender, property)?;883 }884885 Ok(())886 }887888 pub fn delete_collection_property(889 collection: &CollectionHandle<T>,890 sender: &T::CrossAccountId,891 property_key: PropertyKey,892 ) -> DispatchResult {893 collection.check_is_owner_or_admin(sender)?;894895 CollectionProperties::<T>::try_mutate(collection.id, |properties| {896 properties.remove(&property_key)897 })898 .map_err(<Error<T>>::from)?;899900 Self::deposit_event(Event::CollectionPropertyDeleted(901 collection.id,902 property_key,903 ));904905 Ok(())906 }907908 #[transactional]909 pub fn delete_collection_properties(910 collection: &CollectionHandle<T>,911 sender: &T::CrossAccountId,912 property_keys: Vec<PropertyKey>,913 ) -> DispatchResult {914 for key in property_keys {915 Self::delete_collection_property(collection, sender, key)?;916 }917918 Ok(())919 }920921 922 pub fn set_property_permission_unchecked(923 collection: CollectionId,924 property_permission: PropertyKeyPermission,925 ) -> DispatchResult {926 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {927 permissions.try_set(property_permission.key, property_permission.permission)928 })929 .map_err(<Error<T>>::from)?;930 Ok(())931 }932933 pub fn set_property_permission(934 collection: &CollectionHandle<T>,935 sender: &T::CrossAccountId,936 property_permission: PropertyKeyPermission,937 ) -> DispatchResult {938 collection.check_is_owner_or_admin(sender)?;939940 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);941 let current_permission = all_permissions.get(&property_permission.key);942 if matches![943 current_permission,944 Some(PropertyPermission { mutable: false, .. })945 ] {946 return Err(<Error<T>>::NoPermission.into());947 }948949 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {950 let property_permission = property_permission.clone();951 permissions.try_set(property_permission.key, property_permission.permission)952 })953 .map_err(<Error<T>>::from)?;954955 Self::deposit_event(Event::PropertyPermissionSet(956 collection.id,957 property_permission.key,958 ));959960 Ok(())961 }962963 #[transactional]964 pub fn set_token_property_permissions(965 collection: &CollectionHandle<T>,966 sender: &T::CrossAccountId,967 property_permissions: Vec<PropertyKeyPermission>,968 ) -> DispatchResult {969 for prop_pemission in property_permissions {970 Self::set_property_permission(collection, sender, prop_pemission)?;971 }972973 Ok(())974 }975976 pub fn get_collection_property(977 collection_id: CollectionId,978 key: &PropertyKey,979 ) -> Option<PropertyValue> {980 Self::collection_properties(collection_id).get(key).cloned()981 }982983 pub fn bytes_keys_to_property_keys(984 keys: Vec<Vec<u8>>,985 ) -> Result<Vec<PropertyKey>, DispatchError> {986 keys.into_iter()987 .map(|key| -> Result<PropertyKey, DispatchError> {988 key.try_into()989 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())990 })991 .collect::<Result<Vec<PropertyKey>, DispatchError>>()992 }993994 pub fn filter_collection_properties(995 collection_id: CollectionId,996 keys: Option<Vec<PropertyKey>>,997 ) -> Result<Vec<Property>, DispatchError> {998 let properties = Self::collection_properties(collection_id);9991000 let properties = keys1001 .map(|keys| {1002 keys.into_iter()1003 .filter_map(|key| {1004 properties.get(&key).map(|value| Property {1005 key,1006 value: value.clone(),1007 })1008 })1009 .collect()1010 })1011 .unwrap_or_else(|| {1012 properties1013 .into_iter()1014 .map(|(key, value)| Property { key, value })1015 .collect()1016 });10171018 Ok(properties)1019 }10201021 pub fn filter_property_permissions(1022 collection_id: CollectionId,1023 keys: Option<Vec<PropertyKey>>,1024 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1025 let permissions = Self::property_permissions(collection_id);10261027 let key_permissions = keys1028 .map(|keys| {1029 keys.into_iter()1030 .filter_map(|key| {1031 permissions1032 .get(&key)1033 .map(|permission| PropertyKeyPermission {1034 key,1035 permission: permission.clone(),1036 })1037 })1038 .collect()1039 })1040 .unwrap_or_else(|| {1041 permissions1042 .into_iter()1043 .map(|(key, permission)| PropertyKeyPermission { key, permission })1044 .collect()1045 });10461047 Ok(key_permissions)1048 }10491050 pub fn toggle_allowlist(1051 collection: &CollectionHandle<T>,1052 sender: &T::CrossAccountId,1053 user: &T::CrossAccountId,1054 allowed: bool,1055 ) -> DispatchResult {1056 collection.check_is_owner_or_admin(sender)?;10571058 10591060 if allowed {1061 <Allowlist<T>>::insert((collection.id, user), true);1062 } else {1063 <Allowlist<T>>::remove((collection.id, user));1064 }10651066 Ok(())1067 }10681069 pub fn toggle_admin(1070 collection: &CollectionHandle<T>,1071 sender: &T::CrossAccountId,1072 user: &T::CrossAccountId,1073 admin: bool,1074 ) -> DispatchResult {1075 collection.check_is_owner(sender)?;10761077 let was_admin = <IsAdmin<T>>::get((collection.id, user));1078 if was_admin == admin {1079 return Ok(());1080 }1081 let amount = <AdminAmount<T>>::get(collection.id);10821083 if admin {1084 let amount = amount1085 .checked_add(1)1086 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1087 ensure!(1088 amount <= Self::collection_admins_limit(),1089 <Error<T>>::CollectionAdminCountExceeded,1090 );10911092 10931094 <AdminAmount<T>>::insert(collection.id, amount);1095 <IsAdmin<T>>::insert((collection.id, user), true);1096 } else {1097 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1098 <IsAdmin<T>>::remove((collection.id, user));1099 }11001101 Ok(())1102 }11031104 pub fn clamp_limits(1105 mode: CollectionMode,1106 old_limit: &CollectionLimits,1107 mut new_limit: CollectionLimits,1108 ) -> Result<CollectionLimits, DispatchError> {1109 let limits = old_limit;1110 limit_default!(old_limit, new_limit,1111 account_token_ownership_limit => ensure!(1112 new_limit <= MAX_TOKEN_OWNERSHIP,1113 <Error<T>>::CollectionLimitBoundsExceeded,1114 ),1115 sponsored_data_size => ensure!(1116 new_limit <= CUSTOM_DATA_LIMIT,1117 <Error<T>>::CollectionLimitBoundsExceeded,1118 ),11191120 sponsored_data_rate_limit => {},1121 token_limit => ensure!(1122 old_limit >= new_limit && new_limit > 0,1123 <Error<T>>::CollectionTokenLimitExceeded1124 ),11251126 sponsor_transfer_timeout(match mode {1127 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1128 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1129 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1130 }) => ensure!(1131 new_limit <= MAX_SPONSOR_TIMEOUT,1132 <Error<T>>::CollectionLimitBoundsExceeded,1133 ),1134 sponsor_approve_timeout => {},1135 owner_can_transfer => ensure!(1136 !limits.owner_can_transfer_instaled() ||1137 old_limit || !new_limit,1138 <Error<T>>::OwnerPermissionsCantBeReverted,1139 ),1140 owner_can_destroy => ensure!(1141 old_limit || !new_limit,1142 <Error<T>>::OwnerPermissionsCantBeReverted,1143 ),1144 transfers_enabled => {},1145 );1146 Ok(new_limit)1147 }11481149 pub fn clamp_permissions(1150 _mode: CollectionMode,1151 old_limit: &CollectionPermissions,1152 mut new_limit: CollectionPermissions,1153 ) -> Result<CollectionPermissions, DispatchError> {1154 limit_default_clone!(old_limit, new_limit,1155 access => {},1156 mint_mode => {},1157 nesting => { },1158 );1159 Ok(new_limit)1160 }1161}11621163#[macro_export]1164macro_rules! unsupported {1165 () => {1166 Err(<Error<T>>::UnsupportedOperation.into())1167 };1168}116911701171pub trait CommonWeightInfo<CrossAccountId> {1172 fn create_item() -> Weight;1173 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1174 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1175 fn burn_item() -> Weight;1176 fn set_collection_properties(amount: u32) -> Weight;1177 fn delete_collection_properties(amount: u32) -> Weight;1178 fn set_token_properties(amount: u32) -> Weight;1179 fn delete_token_properties(amount: u32) -> Weight;1180 fn set_token_property_permissions(amount: u32) -> Weight;1181 fn transfer() -> Weight;1182 fn approve() -> Weight;1183 fn transfer_from() -> Weight;1184 fn burn_from() -> Weight;11851186 1187 1188 1189 1190 fn burn_recursively_self_raw() -> Weight;1191 1192 1193 1194 fn burn_recursively_breadth_raw(amount: u32) -> Weight;11951196 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1197 Self::burn_recursively_self_raw()1198 .saturating_mul(max_selfs.max(1) as u64)1199 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1200 }1201}12021203pub trait RefungibleExtensionsWeightInfo {1204 fn repartition() -> Weight;1205}12061207pub trait CommonCollectionOperations<T: Config> {1208 fn create_item(1209 &self,1210 sender: T::CrossAccountId,1211 to: T::CrossAccountId,1212 data: CreateItemData,1213 nesting_budget: &dyn Budget,1214 ) -> DispatchResultWithPostInfo;1215 fn create_multiple_items(1216 &self,1217 sender: T::CrossAccountId,1218 to: T::CrossAccountId,1219 data: Vec<CreateItemData>,1220 nesting_budget: &dyn Budget,1221 ) -> DispatchResultWithPostInfo;1222 fn create_multiple_items_ex(1223 &self,1224 sender: T::CrossAccountId,1225 data: CreateItemExData<T::CrossAccountId>,1226 nesting_budget: &dyn Budget,1227 ) -> DispatchResultWithPostInfo;1228 fn burn_item(1229 &self,1230 sender: T::CrossAccountId,1231 token: TokenId,1232 amount: u128,1233 ) -> DispatchResultWithPostInfo;1234 fn burn_item_recursively(1235 &self,1236 sender: T::CrossAccountId,1237 token: TokenId,1238 self_budget: &dyn Budget,1239 breadth_budget: &dyn Budget,1240 ) -> DispatchResultWithPostInfo;1241 fn set_collection_properties(1242 &self,1243 sender: T::CrossAccountId,1244 properties: Vec<Property>,1245 ) -> DispatchResultWithPostInfo;1246 fn delete_collection_properties(1247 &self,1248 sender: &T::CrossAccountId,1249 property_keys: Vec<PropertyKey>,1250 ) -> DispatchResultWithPostInfo;1251 fn set_token_properties(1252 &self,1253 sender: T::CrossAccountId,1254 token_id: TokenId,1255 property: Vec<Property>,1256 nesting_budget: &dyn Budget,1257 ) -> DispatchResultWithPostInfo;1258 fn delete_token_properties(1259 &self,1260 sender: T::CrossAccountId,1261 token_id: TokenId,1262 property_keys: Vec<PropertyKey>,1263 nesting_budget: &dyn Budget,1264 ) -> DispatchResultWithPostInfo;1265 fn set_token_property_permissions(1266 &self,1267 sender: &T::CrossAccountId,1268 property_permissions: Vec<PropertyKeyPermission>,1269 ) -> DispatchResultWithPostInfo;1270 fn transfer(1271 &self,1272 sender: T::CrossAccountId,1273 to: T::CrossAccountId,1274 token: TokenId,1275 amount: u128,1276 nesting_budget: &dyn Budget,1277 ) -> DispatchResultWithPostInfo;1278 fn approve(1279 &self,1280 sender: T::CrossAccountId,1281 spender: T::CrossAccountId,1282 token: TokenId,1283 amount: u128,1284 ) -> DispatchResultWithPostInfo;1285 fn transfer_from(1286 &self,1287 sender: T::CrossAccountId,1288 from: T::CrossAccountId,1289 to: T::CrossAccountId,1290 token: TokenId,1291 amount: u128,1292 nesting_budget: &dyn Budget,1293 ) -> DispatchResultWithPostInfo;1294 fn burn_from(1295 &self,1296 sender: T::CrossAccountId,1297 from: T::CrossAccountId,1298 token: TokenId,1299 amount: u128,1300 nesting_budget: &dyn Budget,1301 ) -> DispatchResultWithPostInfo;13021303 fn check_nesting(1304 &self,1305 sender: T::CrossAccountId,1306 from: (CollectionId, TokenId),1307 under: TokenId,1308 nesting_budget: &dyn Budget,1309 ) -> DispatchResult;13101311 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13121313 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13141315 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1316 fn collection_tokens(&self) -> Vec<TokenId>;1317 fn token_exists(&self, token: TokenId) -> bool;1318 fn last_token_id(&self) -> TokenId;13191320 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1321 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1322 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1323 1324 fn total_supply(&self) -> u32;1325 1326 fn account_balance(&self, account: T::CrossAccountId) -> u32;1327 1328 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1329 fn allowance(1330 &self,1331 sender: T::CrossAccountId,1332 spender: T::CrossAccountId,1333 token: TokenId,1334 ) -> u128;1335 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1336}13371338pub trait RefungibleExtensions<T>1339where1340 T: Config,1341{1342 fn repartition(1343 &self,1344 owner: &T::CrossAccountId,1345 token: TokenId,1346 amount: u128,1347 ) -> DispatchResultWithPostInfo;1348}134913501351pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1352 let post_info = PostDispatchInfo {1353 actual_weight: Some(weight),1354 pays_fee: Pays::Yes,1355 };1356 match res {1357 Ok(()) => Ok(post_info),1358 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1359 }1360}13611362impl<T: Config> From<PropertiesError> for Error<T> {1363 fn from(error: PropertiesError) -> Self {1364 match error {1365 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1366 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1367 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1368 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1369 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1370 }1371 }1372}