12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 weights::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 CollectionId,75 CreateItemData,76 MAX_TOKEN_PREFIX_LENGTH,77 COLLECTION_ADMINS_LIMIT,78 TokenId,79 TokenChild,80 CollectionStats,81 MAX_TOKEN_OWNERSHIP,82 CollectionMode,83 NFT_SPONSOR_TRANSFER_TIMEOUT,84 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,85 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 MAX_SPONSOR_TIMEOUT,87 CUSTOM_DATA_LIMIT,88 CollectionLimits,89 CreateCollectionData,90 SponsorshipState,91 CreateItemExData,92 SponsoringRateLimit,93 budget::Budget,94 PhantomType,95 Property,96 Properties,97 PropertiesPermissionMap,98 PropertyKey,99 PropertyValue,100 PropertyPermission,101 PropertiesError,102 PropertyKeyPermission,103 TokenData,104 TrySetProperty,105 PropertyScope,106 107 RmrkCollectionInfo,108 RmrkInstanceInfo,109 RmrkResourceInfo,110 RmrkPropertyInfo,111 RmrkBaseInfo,112 RmrkPartType,113 RmrkBoundedTheme,114 RmrkNftChild,115 CollectionPermissions,116};117118pub use pallet::*;119use sp_core::H160;120use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};121#[cfg(feature = "runtime-benchmarks")]122pub mod benchmarking;123pub mod dispatch;124pub mod erc;125pub mod eth;126pub mod weights;127128129pub type SelfWeightOf<T> = <T as Config>::WeightInfo;130131132133134135136137#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]138pub struct CollectionHandle<T: Config> {139 140 pub id: CollectionId,141 collection: Collection<T::AccountId>,142 143 pub recorder: SubstrateRecorder<T>,144}145146impl<T: Config> WithRecorder<T> for CollectionHandle<T> {147 fn recorder(&self) -> &SubstrateRecorder<T> {148 &self.recorder149 }150 fn into_recorder(self) -> SubstrateRecorder<T> {151 self.recorder152 }153}154155impl<T: Config> CollectionHandle<T> {156 157 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {158 <CollectionById<T>>::get(id).map(|collection| Self {159 id,160 collection,161 recorder: SubstrateRecorder::new(gas_limit),162 })163 }164165 166 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {167 <CollectionById<T>>::get(id).map(|collection| Self {168 id,169 collection,170 recorder,171 })172 }173174 175 176 pub fn new(id: CollectionId) -> Option<Self> {177 Self::new_with_gas_limit(id, u64::MAX)178 }179180 181 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {182 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)183 }184185 186 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {187 self.recorder188 .consume_gas(T::GasWeightMapping::weight_to_gas(189 Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads)193 ),194 ))195 }196197 198 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {199 self.recorder200 .consume_gas(T::GasWeightMapping::weight_to_gas(201 Weight::from_ref_time(202 <T as frame_system::Config>::DbWeight::get()203 .write204 .saturating_mul(writes)205 ),206 ))207 }208209 210 pub fn consume_store_reads_and_writes(211 &self,212 reads: u64,213 writes: u64,214 ) -> evm_coder::execution::Result<()> {215 let weight = <T as frame_system::Config>::DbWeight::get();216 let reads = weight.read.saturating_mul(reads);217 let writes = weight.read.saturating_mul(writes);218 self.recorder219 .consume_gas(T::GasWeightMapping::weight_to_gas(220 Weight::from_ref_time(reads.saturating_add(writes)),221 ))222 }223224 225 pub fn save(&self) -> DispatchResult {226 <CollectionById<T>>::insert(self.id, &self.collection);227 Ok(())228 }229230 231 232 233 234 235 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {236 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);237 Ok(())238 }239240 241 242 243 244 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {245 if self.collection.sponsorship.pending_sponsor() != Some(sender) {246 return Ok(false);247 }248249 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());250 Ok(true)251 }252253 254 pub fn remove_sponsor(&mut self) -> DispatchResult {255 self.collection.sponsorship = SponsorshipState::Disabled;256 Ok(())257 }258259 260 261 pub fn check_is_internal(&self) -> DispatchResult {262 if self.flags.external {263 return Err(<Error<T>>::CollectionIsExternal)?;264 }265266 Ok(())267 }268269 270 271 pub fn check_is_external(&self) -> DispatchResult {272 if !self.flags.external {273 return Err(<Error<T>>::CollectionIsInternal)?;274 }275276 Ok(())277 }278}279280impl<T: Config> Deref for CollectionHandle<T> {281 type Target = Collection<T::AccountId>;282283 fn deref(&self) -> &Self::Target {284 &self.collection285 }286}287288impl<T: Config> DerefMut for CollectionHandle<T> {289 fn deref_mut(&mut self) -> &mut Self::Target {290 &mut self.collection291 }292}293294impl<T: Config> CollectionHandle<T> {295 296 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {297 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);298 Ok(())299 }300301 302 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {303 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))304 }305306 307 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {308 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);309 Ok(())310 }311312 313 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {314 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)315 }316317 318 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {319 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)320 }321322 323 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {324 ensure!(325 <Allowlist<T>>::get((self.id, user)),326 <Error<T>>::AddressNotInAllowlist327 );328 Ok(())329 }330331 332 333 334 fn set_owner_internal(335 &mut self,336 caller: T::CrossAccountId,337 new_owner: T::CrossAccountId,338 ) -> DispatchResult {339 self.check_is_owner(&caller)?;340 self.collection.owner = new_owner.as_sub().clone();341 self.save()342 }343}344345#[frame_support::pallet]346pub mod pallet {347 use super::*;348 use pallet_evm::account;349 use dispatch::CollectionDispatch;350 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};351 use frame_system::pallet_prelude::*;352 use frame_support::traits::Currency;353 use up_data_structs::{TokenId, mapping::TokenAddressMapping};354 use scale_info::TypeInfo;355 use weights::WeightInfo;356357 #[pallet::config]358 pub trait Config:359 frame_system::Config360 + pallet_evm_coder_substrate::Config361 + pallet_evm::Config362 + TypeInfo363 + account::Config364 {365 366 type WeightInfo: WeightInfo;367368 369 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;370371 372 type Currency: Currency<Self::AccountId>;373374 375 #[pallet::constant]376 type CollectionCreationPrice: Get<377 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,378 >;379380 381 type CollectionDispatch: CollectionDispatch<Self>;382383 384 type TreasuryAccountId: Get<Self::AccountId>;385386 387 type ContractAddress: Get<H160>;388389 390 type EvmTokenAddressMapping: TokenAddressMapping<H160>;391392 393 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;394 }395396 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);397398 #[pallet::pallet]399 #[pallet::storage_version(STORAGE_VERSION)]400 #[pallet::generate_store(pub(super) trait Store)]401 pub struct Pallet<T>(_);402403 #[pallet::extra_constants]404 impl<T: Config> Pallet<T> {405 406 pub fn collection_admins_limit() -> u32 {407 COLLECTION_ADMINS_LIMIT408 }409 }410411 #[pallet::event]412 #[pallet::generate_deposit(pub fn deposit_event)]413 pub enum Event<T: Config> {414 415 CollectionCreated(416 417 CollectionId,418 419 u8,420 421 T::AccountId,422 ),423424 425 CollectionDestroyed(426 427 CollectionId,428 ),429430 431 ItemCreated(432 433 CollectionId,434 435 TokenId,436 437 T::CrossAccountId,438 439 u128,440 ),441442 443 ItemDestroyed(444 445 CollectionId,446 447 TokenId,448 449 T::CrossAccountId,450 451 u128,452 ),453454 455 Transfer(456 457 CollectionId,458 459 TokenId,460 461 T::CrossAccountId,462 463 T::CrossAccountId,464 465 u128,466 ),467468 469 Approved(470 471 CollectionId,472 473 TokenId,474 475 T::CrossAccountId,476 477 T::CrossAccountId,478 479 u128,480 ),481482 483 CollectionPropertySet(484 485 CollectionId,486 487 PropertyKey,488 ),489490 491 CollectionPropertyDeleted(492 493 CollectionId,494 495 PropertyKey,496 ),497498 499 TokenPropertySet(500 501 CollectionId,502 503 TokenId,504 505 PropertyKey,506 ),507508 509 TokenPropertyDeleted(510 511 CollectionId,512 513 TokenId,514 515 PropertyKey,516 ),517518 519 PropertyPermissionSet(520 521 CollectionId,522 523 PropertyKey,524 ),525 }526527 #[pallet::error]528 pub enum Error<T> {529 530 CollectionNotFound,531 532 MustBeTokenOwner,533 534 NoPermission,535 536 CantDestroyNotEmptyCollection,537 538 PublicMintingNotAllowed,539 540 AddressNotInAllowlist,541542 543 CollectionNameLimitExceeded,544 545 CollectionDescriptionLimitExceeded,546 547 CollectionTokenPrefixLimitExceeded,548 549 TotalCollectionsLimitExceeded,550 551 CollectionAdminCountExceeded,552 553 CollectionLimitBoundsExceeded,554 555 OwnerPermissionsCantBeReverted,556 557 TransferNotAllowed,558 559 AccountTokenLimitExceeded,560 561 CollectionTokenLimitExceeded,562 563 MetadataFlagFrozen,564565 566 TokenNotFound,567 568 TokenValueTooLow,569 570 ApprovedValueTooLow,571 572 CantApproveMoreThanOwned,573574 575 AddressIsZero,576577 578 UnsupportedOperation,579580 581 NotSufficientFounds,582583 584 UserIsNotAllowedToNest,585 586 SourceCollectionIsNotAllowedToNest,587588 589 CollectionFieldSizeExceeded,590591 592 NoSpaceForProperty,593594 595 PropertyLimitReached,596597 598 PropertyKeyIsTooLong,599600 601 InvalidCharacterInPropertyKey,602603 604 EmptyPropertyKey,605606 607 CollectionIsExternal,608609 610 CollectionIsInternal,611 }612613 614 #[pallet::storage]615 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;616617 618 #[pallet::storage]619 pub type DestroyedCollectionCount<T> =620 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;621622 623 #[pallet::storage]624 pub type CollectionById<T> = StorageMap<625 Hasher = Blake2_128Concat,626 Key = CollectionId,627 Value = Collection<<T as frame_system::Config>::AccountId>,628 QueryKind = OptionQuery,629 >;630631 632 #[pallet::storage]633 #[pallet::getter(fn collection_properties)]634 pub type CollectionProperties<T> = StorageMap<635 Hasher = Blake2_128Concat,636 Key = CollectionId,637 Value = Properties,638 QueryKind = ValueQuery,639 OnEmpty = up_data_structs::CollectionProperties,640 >;641642 643 #[pallet::storage]644 #[pallet::getter(fn property_permissions)]645 pub type CollectionPropertyPermissions<T> = StorageMap<646 Hasher = Blake2_128Concat,647 Key = CollectionId,648 Value = PropertiesPermissionMap,649 QueryKind = ValueQuery,650 >;651652 653 #[pallet::storage]654 pub type AdminAmount<T> = StorageMap<655 Hasher = Blake2_128Concat,656 Key = CollectionId,657 Value = u32,658 QueryKind = ValueQuery,659 >;660661 662 #[pallet::storage]663 pub type IsAdmin<T: Config> = StorageNMap<664 Key = (665 Key<Blake2_128Concat, CollectionId>,666 Key<Blake2_128Concat, T::CrossAccountId>,667 ),668 Value = bool,669 QueryKind = ValueQuery,670 >;671672 673 #[pallet::storage]674 pub type Allowlist<T: Config> = StorageNMap<675 Key = (676 Key<Blake2_128Concat, CollectionId>,677 Key<Blake2_128Concat, T::CrossAccountId>,678 ),679 Value = bool,680 QueryKind = ValueQuery,681 >;682683 684 #[pallet::storage]685 pub type DummyStorageValue<T: Config> = StorageValue<686 Value = (687 CollectionStats,688 CollectionId,689 TokenId,690 TokenChild,691 PhantomType<(692 TokenData<T::CrossAccountId>,693 RpcCollection<T::AccountId>,694 695 RmrkCollectionInfo<T::AccountId>,696 RmrkInstanceInfo<T::AccountId>,697 RmrkResourceInfo,698 RmrkPropertyInfo,699 RmrkBaseInfo<T::AccountId>,700 RmrkPartType,701 RmrkBoundedTheme,702 RmrkNftChild,703 )>,704 ),705 QueryKind = OptionQuery,706 >;707708 #[pallet::hooks]709 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {710 fn on_runtime_upgrade() -> Weight {711 StorageVersion::new(1).put::<Pallet<T>>();712713 Weight::zero()714 }715 }716}717718impl<T: Config> Pallet<T> {719 720 721 722 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {723 ensure!(724 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,725 <Error<T>>::AddressIsZero726 );727 Ok(())728 }729730 731 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732 <IsAdmin<T>>::iter_prefix((collection,))733 .map(|(a, _)| a)734 .collect()735 }736737 738 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {739 <Allowlist<T>>::iter_prefix((collection,))740 .map(|(a, _)| a)741 .collect()742 }743744 745 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {746 <Allowlist<T>>::get((collection, user))747 }748749 750 pub fn collection_stats() -> CollectionStats {751 let created = <CreatedCollectionCount<T>>::get();752 let destroyed = <DestroyedCollectionCount<T>>::get();753 CollectionStats {754 created: created.0,755 destroyed: destroyed.0,756 alive: created.0 - destroyed.0,757 }758 }759760 761 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {762 let collection = <CollectionById<T>>::get(collection)?;763 let limits = collection.limits;764 let effective_limits = CollectionLimits {765 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),766 sponsored_data_size: Some(limits.sponsored_data_size()),767 sponsored_data_rate_limit: Some(768 limits769 .sponsored_data_rate_limit770 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),771 ),772 token_limit: Some(limits.token_limit()),773 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(774 match collection.mode {775 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,776 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,777 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,778 },779 )),780 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),781 owner_can_transfer: Some(limits.owner_can_transfer()),782 owner_can_destroy: Some(limits.owner_can_destroy()),783 transfers_enabled: Some(limits.transfers_enabled()),784 };785786 Some(effective_limits)787 }788789 790 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {791 let Collection {792 name,793 description,794 owner,795 mode,796 token_prefix,797 sponsorship,798 limits,799 permissions,800 flags,801 } = <CollectionById<T>>::get(collection)?;802803 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)804 .into_iter()805 .map(|(key, permission)| PropertyKeyPermission { key, permission })806 .collect();807808 let properties = <CollectionProperties<T>>::get(collection)809 .into_iter()810 .map(|(key, value)| Property { key, value })811 .collect();812813 let permissions = CollectionPermissions {814 access: Some(permissions.access()),815 mint_mode: Some(permissions.mint_mode()),816 nesting: Some(permissions.nesting().clone()),817 };818819 Some(RpcCollection {820 name: name.into_inner(),821 description: description.into_inner(),822 owner,823 mode,824 token_prefix: token_prefix.into_inner(),825 sponsorship,826 limits,827 permissions,828 token_property_permissions,829 properties,830 read_only: flags.external,831 foreign: flags.foreign,832 })833 }834}835836macro_rules! limit_default {837 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{838 $(839 if let Some($new) = $new.$field {840 let $old = $old.$field($($arg)?);841 let _ = $new;842 let _ = $old;843 $check844 } else {845 $new.$field = $old.$field846 }847 )*848 }};849}850macro_rules! limit_default_clone {851 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{852 $(853 if let Some($new) = $new.$field.clone() {854 let $old = $old.$field($($arg)?);855 let _ = $new;856 let _ = $old;857 $check858 } else {859 $new.$field = $old.$field.clone()860 }861 )*862 }};863}864865impl<T: Config> Pallet<T> {866 867 868 869 870 871 pub fn init_collection(872 owner: T::CrossAccountId,873 payer: T::CrossAccountId,874 data: CreateCollectionData<T::AccountId>,875 flags: CollectionFlags,876 ) -> Result<CollectionId, DispatchError> {877 {878 ensure!(879 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,880 Error::<T>::CollectionTokenPrefixLimitExceeded881 );882 }883884 let created_count = <CreatedCollectionCount<T>>::get()885 .0886 .checked_add(1)887 .ok_or(ArithmeticError::Overflow)?;888 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;889 let id = CollectionId(created_count);890891 892 ensure!(893 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,894 <Error<T>>::TotalCollectionsLimitExceeded895 );896897 898899 let collection = Collection {900 owner: owner.as_sub().clone(),901 name: data.name,902 mode: data.mode.clone(),903 description: data.description,904 token_prefix: data.token_prefix,905 sponsorship: data906 .pending_sponsor907 .map(SponsorshipState::Unconfirmed)908 .unwrap_or_default(),909 limits: data910 .limits911 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))912 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,913 permissions: data914 .permissions915 .map(|permissions| {916 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)917 })918 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,919 flags,920 };921922 let mut collection_properties = up_data_structs::CollectionProperties::get();923 collection_properties924 .try_set_from_iter(data.properties.into_iter())925 .map_err(<Error<T>>::from)?;926927 CollectionProperties::<T>::insert(id, collection_properties);928929 let mut token_props_permissions = PropertiesPermissionMap::new();930 token_props_permissions931 .try_set_from_iter(data.token_property_permissions.into_iter())932 .map_err(<Error<T>>::from)?;933934 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);935936 937 {938 let mut imbalance =939 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();940 imbalance.subsume(941 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(942 &T::TreasuryAccountId::get(),943 T::CollectionCreationPrice::get(),944 ),945 );946 <T as Config>::Currency::settle(947 payer.as_sub(),948 imbalance,949 WithdrawReasons::TRANSFER,950 ExistenceRequirement::KeepAlive,951 )952 .map_err(|_| Error::<T>::NotSufficientFounds)?;953 }954955 <CreatedCollectionCount<T>>::put(created_count);956 <Pallet<T>>::deposit_event(Event::CollectionCreated(957 id,958 data.mode.id(),959 owner.as_sub().clone(),960 ));961 <PalletEvm<T>>::deposit_log(962 erc::CollectionHelpersEvents::CollectionCreated {963 owner: *owner.as_eth(),964 collection_id: eth::collection_id_to_address(id),965 }966 .to_log(T::ContractAddress::get()),967 );968 <CollectionById<T>>::insert(id, collection);969 Ok(id)970 }971972 973 974 975 976 pub fn destroy_collection(977 collection: CollectionHandle<T>,978 sender: &T::CrossAccountId,979 ) -> DispatchResult {980 ensure!(981 collection.limits.owner_can_destroy(),982 <Error<T>>::NoPermission,983 );984 collection.check_is_owner(sender)?;985986 let destroyed_collections = <DestroyedCollectionCount<T>>::get()987 .0988 .checked_add(1)989 .ok_or(ArithmeticError::Overflow)?;990991 992993 <DestroyedCollectionCount<T>>::put(destroyed_collections);994 <CollectionById<T>>::remove(collection.id);995 <AdminAmount<T>>::remove(collection.id);996 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);997 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);998 <CollectionProperties<T>>::remove(collection.id);9991000 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));1001 Ok(())1002 }10031004 1005 1006 1007 1008 1009 pub fn set_collection_property(1010 collection: &CollectionHandle<T>,1011 sender: &T::CrossAccountId,1012 property: Property,1013 ) -> DispatchResult {1014 collection.check_is_owner_or_admin(sender)?;10151016 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1017 let property = property.clone();1018 properties.try_set(property.key, property.value)1019 })1020 .map_err(<Error<T>>::from)?;10211022 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10231024 Ok(())1025 }10261027 1028 1029 1030 1031 1032 pub fn set_scoped_collection_property(1033 collection_id: CollectionId,1034 scope: PropertyScope,1035 property: Property,1036 ) -> DispatchResult {1037 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1038 properties.try_scoped_set(scope, property.key, property.value)1039 })1040 .map_err(<Error<T>>::from)?;10411042 Ok(())1043 }10441045 1046 1047 1048 1049 1050 pub fn set_scoped_collection_properties(1051 collection_id: CollectionId,1052 scope: PropertyScope,1053 properties: impl Iterator<Item = Property>,1054 ) -> DispatchResult {1055 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1056 stored_properties.try_scoped_set_from_iter(scope, properties)1057 })1058 .map_err(<Error<T>>::from)?;10591060 Ok(())1061 }10621063 1064 1065 1066 1067 1068 #[transactional]1069 pub fn set_collection_properties(1070 collection: &CollectionHandle<T>,1071 sender: &T::CrossAccountId,1072 properties: Vec<Property>,1073 ) -> DispatchResult {1074 for property in properties {1075 Self::set_collection_property(collection, sender, property)?;1076 }10771078 Ok(())1079 }10801081 1082 1083 1084 1085 1086 pub fn delete_collection_property(1087 collection: &CollectionHandle<T>,1088 sender: &T::CrossAccountId,1089 property_key: PropertyKey,1090 ) -> DispatchResult {1091 collection.check_is_owner_or_admin(sender)?;10921093 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1094 properties.remove(&property_key)1095 })1096 .map_err(<Error<T>>::from)?;10971098 Self::deposit_event(Event::CollectionPropertyDeleted(1099 collection.id,1100 property_key,1101 ));11021103 Ok(())1104 }11051106 1107 1108 1109 1110 1111 #[transactional]1112 pub fn delete_collection_properties(1113 collection: &CollectionHandle<T>,1114 sender: &T::CrossAccountId,1115 property_keys: Vec<PropertyKey>,1116 ) -> DispatchResult {1117 for key in property_keys {1118 Self::delete_collection_property(collection, sender, key)?;1119 }11201121 Ok(())1122 }11231124 1125 1126 1127 1128 1129 1130 pub fn set_property_permission_unchecked(1131 collection: CollectionId,1132 property_permission: PropertyKeyPermission,1133 ) -> DispatchResult {1134 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1135 permissions.try_set(property_permission.key, property_permission.permission)1136 })1137 .map_err(<Error<T>>::from)?;1138 Ok(())1139 }11401141 1142 1143 1144 1145 1146 pub fn set_property_permission(1147 collection: &CollectionHandle<T>,1148 sender: &T::CrossAccountId,1149 property_permission: PropertyKeyPermission,1150 ) -> DispatchResult {1151 Self::set_scoped_property_permission(1152 collection,1153 sender,1154 PropertyScope::None,1155 property_permission,1156 )1157 }11581159 1160 1161 1162 1163 1164 1165 pub fn set_scoped_property_permission(1166 collection: &CollectionHandle<T>,1167 sender: &T::CrossAccountId,1168 scope: PropertyScope,1169 property_permission: PropertyKeyPermission,1170 ) -> DispatchResult {1171 collection.check_is_owner_or_admin(sender)?;11721173 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1174 let current_permission = all_permissions.get(&property_permission.key);1175 if matches![1176 current_permission,1177 Some(PropertyPermission { mutable: false, .. })1178 ] {1179 return Err(<Error<T>>::NoPermission.into());1180 }11811182 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1183 let property_permission = property_permission.clone();1184 permissions.try_scoped_set(1185 scope,1186 property_permission.key,1187 property_permission.permission,1188 )1189 })1190 .map_err(<Error<T>>::from)?;11911192 Self::deposit_event(Event::PropertyPermissionSet(1193 collection.id,1194 property_permission.key,1195 ));11961197 Ok(())1198 }11991200 1201 1202 1203 1204 1205 #[transactional]1206 pub fn set_token_property_permissions(1207 collection: &CollectionHandle<T>,1208 sender: &T::CrossAccountId,1209 property_permissions: Vec<PropertyKeyPermission>,1210 ) -> DispatchResult {1211 Self::set_scoped_token_property_permissions(1212 collection,1213 sender,1214 PropertyScope::None,1215 property_permissions,1216 )1217 }12181219 1220 1221 1222 1223 1224 1225 #[transactional]1226 pub fn set_scoped_token_property_permissions(1227 collection: &CollectionHandle<T>,1228 sender: &T::CrossAccountId,1229 scope: PropertyScope,1230 property_permissions: Vec<PropertyKeyPermission>,1231 ) -> DispatchResult {1232 for prop_pemission in property_permissions {1233 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1234 }12351236 Ok(())1237 }12381239 1240 pub fn get_collection_property(1241 collection_id: CollectionId,1242 key: &PropertyKey,1243 ) -> Option<PropertyValue> {1244 Self::collection_properties(collection_id).get(key).cloned()1245 }12461247 1248 pub fn bytes_keys_to_property_keys(1249 keys: Vec<Vec<u8>>,1250 ) -> Result<Vec<PropertyKey>, DispatchError> {1251 keys.into_iter()1252 .map(|key| -> Result<PropertyKey, DispatchError> {1253 key.try_into()1254 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1255 })1256 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1257 }12581259 1260 pub fn filter_collection_properties(1261 collection_id: CollectionId,1262 keys: Option<Vec<PropertyKey>>,1263 ) -> Result<Vec<Property>, DispatchError> {1264 let properties = Self::collection_properties(collection_id);12651266 let properties = keys1267 .map(|keys| {1268 keys.into_iter()1269 .filter_map(|key| {1270 properties.get(&key).map(|value| Property {1271 key,1272 value: value.clone(),1273 })1274 })1275 .collect()1276 })1277 .unwrap_or_else(|| {1278 properties1279 .into_iter()1280 .map(|(key, value)| Property { key, value })1281 .collect()1282 });12831284 Ok(properties)1285 }12861287 1288 pub fn filter_property_permissions(1289 collection_id: CollectionId,1290 keys: Option<Vec<PropertyKey>>,1291 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1292 let permissions = Self::property_permissions(collection_id);12931294 let key_permissions = keys1295 .map(|keys| {1296 keys.into_iter()1297 .filter_map(|key| {1298 permissions1299 .get(&key)1300 .map(|permission| PropertyKeyPermission {1301 key,1302 permission: permission.clone(),1303 })1304 })1305 .collect()1306 })1307 .unwrap_or_else(|| {1308 permissions1309 .into_iter()1310 .map(|(key, permission)| PropertyKeyPermission { key, permission })1311 .collect()1312 });13131314 Ok(key_permissions)1315 }13161317 1318 1319 1320 pub fn toggle_allowlist(1321 collection: &CollectionHandle<T>,1322 sender: &T::CrossAccountId,1323 user: &T::CrossAccountId,1324 allowed: bool,1325 ) -> DispatchResult {1326 collection.check_is_owner_or_admin(sender)?;13271328 13291330 if allowed {1331 <Allowlist<T>>::insert((collection.id, user), true);1332 } else {1333 <Allowlist<T>>::remove((collection.id, user));1334 }13351336 Ok(())1337 }13381339 1340 1341 1342 pub fn toggle_admin(1343 collection: &CollectionHandle<T>,1344 sender: &T::CrossAccountId,1345 user: &T::CrossAccountId,1346 admin: bool,1347 ) -> DispatchResult {1348 collection.check_is_owner(sender)?;13491350 let was_admin = <IsAdmin<T>>::get((collection.id, user));1351 if was_admin == admin {1352 return Ok(());1353 }1354 let amount = <AdminAmount<T>>::get(collection.id);13551356 if admin {1357 let amount = amount1358 .checked_add(1)1359 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1360 ensure!(1361 amount <= Self::collection_admins_limit(),1362 <Error<T>>::CollectionAdminCountExceeded,1363 );13641365 13661367 <AdminAmount<T>>::insert(collection.id, amount);1368 <IsAdmin<T>>::insert((collection.id, user), true);1369 } else {1370 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1371 <IsAdmin<T>>::remove((collection.id, user));1372 }13731374 Ok(())1375 }13761377 1378 pub fn clamp_limits(1379 mode: CollectionMode,1380 old_limit: &CollectionLimits,1381 mut new_limit: CollectionLimits,1382 ) -> Result<CollectionLimits, DispatchError> {1383 let limits = old_limit;1384 limit_default!(old_limit, new_limit,1385 account_token_ownership_limit => ensure!(1386 new_limit <= MAX_TOKEN_OWNERSHIP,1387 <Error<T>>::CollectionLimitBoundsExceeded,1388 ),1389 sponsored_data_size => ensure!(1390 new_limit <= CUSTOM_DATA_LIMIT,1391 <Error<T>>::CollectionLimitBoundsExceeded,1392 ),13931394 sponsored_data_rate_limit => {},1395 token_limit => ensure!(1396 old_limit >= new_limit && new_limit > 0,1397 <Error<T>>::CollectionTokenLimitExceeded1398 ),13991400 sponsor_transfer_timeout(match mode {1401 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1402 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1403 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1404 }) => ensure!(1405 new_limit <= MAX_SPONSOR_TIMEOUT,1406 <Error<T>>::CollectionLimitBoundsExceeded,1407 ),1408 sponsor_approve_timeout => {},1409 owner_can_transfer => ensure!(1410 !limits.owner_can_transfer_instaled() ||1411 old_limit || !new_limit,1412 <Error<T>>::OwnerPermissionsCantBeReverted,1413 ),1414 owner_can_destroy => ensure!(1415 old_limit || !new_limit,1416 <Error<T>>::OwnerPermissionsCantBeReverted,1417 ),1418 transfers_enabled => {},1419 );1420 Ok(new_limit)1421 }14221423 1424 pub fn clamp_permissions(1425 _mode: CollectionMode,1426 old_permission: &CollectionPermissions,1427 mut new_permission: CollectionPermissions,1428 ) -> Result<CollectionPermissions, DispatchError> {1429 limit_default_clone!(old_permission, new_permission,1430 access => {},1431 mint_mode => {},1432 nesting => { },1433 );1434 Ok(new_permission)1435 }1436}143714381439#[macro_export]1440macro_rules! unsupported {1441 ($runtime:path) => {1442 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1443 };1444}144514461447pub trait CommonWeightInfo<CrossAccountId> {1448 1449 fn create_item() -> Weight;14501451 1452 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14531454 1455 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14561457 1458 fn burn_item() -> Weight;14591460 1461 1462 1463 fn set_collection_properties(amount: u32) -> Weight;14641465 1466 1467 1468 fn delete_collection_properties(amount: u32) -> Weight;14691470 1471 1472 1473 fn set_token_properties(amount: u32) -> Weight;14741475 1476 1477 1478 fn delete_token_properties(amount: u32) -> Weight;14791480 1481 1482 1483 fn set_token_property_permissions(amount: u32) -> Weight;14841485 1486 fn transfer() -> Weight;14871488 1489 fn approve() -> Weight;14901491 1492 fn transfer_from() -> Weight;14931494 1495 fn burn_from() -> Weight;14961497 1498 1499 1500 1501 fn burn_recursively_self_raw() -> Weight;15021503 1504 1505 1506 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15071508 1509 1510 1511 1512 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1513 Self::burn_recursively_self_raw()1514 .saturating_mul(max_selfs.max(1) as u64)1515 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1516 }15171518 1519 fn token_owner() -> Weight;1520}152115221523pub trait RefungibleExtensionsWeightInfo {1524 1525 fn repartition() -> Weight;1526}152715281529153015311532pub trait CommonCollectionOperations<T: Config> {1533 1534 1535 1536 1537 1538 1539 fn create_item(1540 &self,1541 sender: T::CrossAccountId,1542 to: T::CrossAccountId,1543 data: CreateItemData,1544 nesting_budget: &dyn Budget,1545 ) -> DispatchResultWithPostInfo;15461547 1548 1549 1550 1551 1552 1553 fn create_multiple_items(1554 &self,1555 sender: T::CrossAccountId,1556 to: T::CrossAccountId,1557 data: Vec<CreateItemData>,1558 nesting_budget: &dyn Budget,1559 ) -> DispatchResultWithPostInfo;15601561 1562 1563 1564 1565 1566 1567 fn create_multiple_items_ex(1568 &self,1569 sender: T::CrossAccountId,1570 data: CreateItemExData<T::CrossAccountId>,1571 nesting_budget: &dyn Budget,1572 ) -> DispatchResultWithPostInfo;15731574 1575 1576 1577 1578 1579 fn burn_item(1580 &self,1581 sender: T::CrossAccountId,1582 token: TokenId,1583 amount: u128,1584 ) -> DispatchResultWithPostInfo;15851586 1587 1588 1589 1590 1591 1592 fn burn_item_recursively(1593 &self,1594 sender: T::CrossAccountId,1595 token: TokenId,1596 self_budget: &dyn Budget,1597 breadth_budget: &dyn Budget,1598 ) -> DispatchResultWithPostInfo;15991600 1601 1602 1603 1604 fn set_collection_properties(1605 &self,1606 sender: T::CrossAccountId,1607 properties: Vec<Property>,1608 ) -> DispatchResultWithPostInfo;16091610 1611 1612 1613 1614 fn delete_collection_properties(1615 &self,1616 sender: &T::CrossAccountId,1617 property_keys: Vec<PropertyKey>,1618 ) -> DispatchResultWithPostInfo;16191620 1621 1622 1623 1624 1625 1626 1627 1628 1629 fn set_token_properties(1630 &self,1631 sender: T::CrossAccountId,1632 token_id: TokenId,1633 properties: Vec<Property>,1634 budget: &dyn Budget,1635 ) -> DispatchResultWithPostInfo;16361637 1638 1639 1640 1641 1642 1643 1644 1645 1646 fn delete_token_properties(1647 &self,1648 sender: T::CrossAccountId,1649 token_id: TokenId,1650 property_keys: Vec<PropertyKey>,1651 budget: &dyn Budget,1652 ) -> DispatchResultWithPostInfo;16531654 1655 1656 1657 1658 1659 1660 fn set_token_property_permissions(1661 &self,1662 sender: &T::CrossAccountId,1663 property_permissions: Vec<PropertyKeyPermission>,1664 ) -> DispatchResultWithPostInfo;16651666 1667 1668 1669 1670 1671 1672 1673 fn transfer(1674 &self,1675 sender: T::CrossAccountId,1676 to: T::CrossAccountId,1677 token: TokenId,1678 amount: u128,1679 budget: &dyn Budget,1680 ) -> DispatchResultWithPostInfo;16811682 1683 1684 1685 1686 1687 1688 fn approve(1689 &self,1690 sender: T::CrossAccountId,1691 spender: T::CrossAccountId,1692 token: TokenId,1693 amount: u128,1694 ) -> DispatchResultWithPostInfo;16951696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 fn transfer_from(1707 &self,1708 sender: T::CrossAccountId,1709 from: T::CrossAccountId,1710 to: T::CrossAccountId,1711 token: TokenId,1712 amount: u128,1713 budget: &dyn Budget,1714 ) -> DispatchResultWithPostInfo;17151716 1717 1718 1719 1720 1721 1722 1723 1724 1725 fn burn_from(1726 &self,1727 sender: T::CrossAccountId,1728 from: T::CrossAccountId,1729 token: TokenId,1730 amount: u128,1731 budget: &dyn Budget,1732 ) -> DispatchResultWithPostInfo;17331734 1735 1736 1737 1738 1739 1740 fn check_nesting(1741 &self,1742 sender: T::CrossAccountId,1743 from: (CollectionId, TokenId),1744 under: TokenId,1745 budget: &dyn Budget,1746 ) -> DispatchResult;17471748 1749 1750 1751 1752 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17531754 1755 1756 1757 1758 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17591760 1761 1762 1763 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17641765 1766 fn collection_tokens(&self) -> Vec<TokenId>;17671768 1769 1770 1771 fn token_exists(&self, token: TokenId) -> bool;17721773 1774 fn last_token_id(&self) -> TokenId;17751776 1777 1778 1779 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17801781 1782 1783 1784 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17851786 1787 1788 1789 1790 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17911792 1793 1794 1795 1796 1797 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17981799 1800 fn total_supply(&self) -> u32;18011802 1803 1804 1805 fn account_balance(&self, account: T::CrossAccountId) -> u32;18061807 1808 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18091810 1811 fn total_pieces(&self, token: TokenId) -> Option<u128>;18121813 1814 1815 1816 1817 1818 fn allowance(1819 &self,1820 sender: T::CrossAccountId,1821 spender: T::CrossAccountId,1822 token: TokenId,1823 ) -> u128;18241825 1826 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1827}182818291830pub trait RefungibleExtensions<T>1831where1832 T: Config,1833{1834 1835 1836 1837 1838 1839 1840 1841 fn repartition(1842 &self,1843 sender: &T::CrossAccountId,1844 token: TokenId,1845 amount: u128,1846 ) -> DispatchResultWithPostInfo;1847}18481849185018511852pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1853 let post_info = PostDispatchInfo {1854 actual_weight: Some(weight),1855 pays_fee: Pays::Yes,1856 };1857 match res {1858 Ok(()) => Ok(post_info),1859 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1860 }1861}18621863impl<T: Config> From<PropertiesError> for Error<T> {1864 fn from(error: PropertiesError) -> Self {1865 match error {1866 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1867 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1868 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1869 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1870 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1871 }1872 }1873}