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 CollectionId,74 CreateItemData,75 MAX_TOKEN_PREFIX_LENGTH,76 COLLECTION_ADMINS_LIMIT,77 TokenId,78 TokenChild,79 CollectionStats,80 MAX_TOKEN_OWNERSHIP,81 CollectionMode,82 NFT_SPONSOR_TRANSFER_TIMEOUT,83 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,84 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,85 MAX_SPONSOR_TIMEOUT,86 CUSTOM_DATA_LIMIT,87 CollectionLimits,88 CreateCollectionData,89 SponsorshipState,90 CreateItemExData,91 SponsoringRateLimit,92 budget::Budget,93 PhantomType,94 Property,95 Properties,96 PropertiesPermissionMap,97 PropertyKey,98 PropertyValue,99 PropertyPermission,100 PropertiesError,101 PropertyKeyPermission,102 TokenData,103 TrySetProperty,104 PropertyScope,105 106 RmrkCollectionInfo,107 RmrkInstanceInfo,108 RmrkResourceInfo,109 RmrkPropertyInfo,110 RmrkBaseInfo,111 RmrkPartType,112 RmrkBoundedTheme,113 RmrkNftChild,114 CollectionPermissions,115 SchemaVersion,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 <T as frame_system::Config>::DbWeight::get()190 .read191 .saturating_mul(reads),192 ))193 }194195 196 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {197 self.recorder198 .consume_gas(T::GasWeightMapping::weight_to_gas(199 <T as frame_system::Config>::DbWeight::get()200 .write201 .saturating_mul(writes),202 ))203 }204205 206 pub fn save(self) -> DispatchResult {207 <CollectionById<T>>::insert(self.id, self.collection);208 Ok(())209 }210211 212 213 214 215 216 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {217 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);218 Ok(())219 }220221 222 223 224 225 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {226 if self.collection.sponsorship.pending_sponsor() != Some(sender) {227 return Ok(false);228 }229230 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());231 Ok(true)232 }233234 235 236 pub fn check_is_internal(&self) -> DispatchResult {237 if self.external_collection {238 return Err(<Error<T>>::CollectionIsExternal)?;239 }240241 Ok(())242 }243244 245 246 pub fn check_is_external(&self) -> DispatchResult {247 if !self.external_collection {248 return Err(<Error<T>>::CollectionIsInternal)?;249 }250251 Ok(())252 }253}254255impl<T: Config> Deref for CollectionHandle<T> {256 type Target = Collection<T::AccountId>;257258 fn deref(&self) -> &Self::Target {259 &self.collection260 }261}262263impl<T: Config> DerefMut for CollectionHandle<T> {264 fn deref_mut(&mut self) -> &mut Self::Target {265 &mut self.collection266 }267}268269impl<T: Config> CollectionHandle<T> {270 271 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {272 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);273 Ok(())274 }275276 277 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {278 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))279 }280281 282 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {283 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);284 Ok(())285 }286287 288 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {289 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)290 }291292 293 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {294 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)295 }296297 298 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {299 ensure!(300 <Allowlist<T>>::get((self.id, user)),301 <Error<T>>::AddressNotInAllowlist302 );303 Ok(())304 }305}306307#[frame_support::pallet]308pub mod pallet {309 use super::*;310 use pallet_evm::account;311 use dispatch::CollectionDispatch;312 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};313 use frame_system::pallet_prelude::*;314 use frame_support::traits::Currency;315 use up_data_structs::{TokenId, mapping::TokenAddressMapping};316 use scale_info::TypeInfo;317 use weights::WeightInfo;318319 #[pallet::config]320 pub trait Config:321 frame_system::Config322 + pallet_evm_coder_substrate::Config323 + pallet_evm::Config324 + TypeInfo325 + account::Config326 {327 328 type WeightInfo: WeightInfo;329330 331 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;332333 334 type Currency: Currency<Self::AccountId>;335336 337 #[pallet::constant]338 type CollectionCreationPrice: Get<339 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,340 >;341342 343 type CollectionDispatch: CollectionDispatch<Self>;344345 346 type TreasuryAccountId: Get<Self::AccountId>;347348 349 type ContractAddress: Get<H160>;350351 352 type EvmTokenAddressMapping: TokenAddressMapping<H160>;353354 355 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;356 }357358 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);359360 #[pallet::pallet]361 #[pallet::storage_version(STORAGE_VERSION)]362 #[pallet::generate_store(pub(super) trait Store)]363 pub struct Pallet<T>(_);364365 #[pallet::extra_constants]366 impl<T: Config> Pallet<T> {367 368 pub fn collection_admins_limit() -> u32 {369 COLLECTION_ADMINS_LIMIT370 }371 }372373 #[pallet::event]374 #[pallet::generate_deposit(pub fn deposit_event)]375 pub enum Event<T: Config> {376 377 CollectionCreated(378 379 CollectionId,380 381 u8,382 383 T::AccountId,384 ),385386 387 CollectionDestroyed(388 389 CollectionId,390 ),391392 393 ItemCreated(394 395 CollectionId,396 397 TokenId,398 399 T::CrossAccountId,400 401 u128,402 ),403404 405 ItemDestroyed(406 407 CollectionId,408 409 TokenId,410 411 T::CrossAccountId,412 413 u128,414 ),415416 417 Transfer(418 419 CollectionId,420 421 TokenId,422 423 T::CrossAccountId,424 425 T::CrossAccountId,426 427 u128,428 ),429430 431 Approved(432 433 CollectionId,434 435 TokenId,436 437 T::CrossAccountId,438 439 T::CrossAccountId,440 441 u128,442 ),443444 445 CollectionPropertySet(446 447 CollectionId,448 449 PropertyKey,450 ),451452 453 CollectionPropertyDeleted(454 455 CollectionId,456 457 PropertyKey,458 ),459460 461 TokenPropertySet(462 463 CollectionId,464 465 TokenId,466 467 PropertyKey,468 ),469470 471 TokenPropertyDeleted(472 473 CollectionId,474 475 TokenId,476 477 PropertyKey,478 ),479480 481 PropertyPermissionSet(482 483 CollectionId,484 485 PropertyKey,486 ),487 }488489 #[pallet::error]490 pub enum Error<T> {491 492 CollectionNotFound,493 494 MustBeTokenOwner,495 496 NoPermission,497 498 CantDestroyNotEmptyCollection,499 500 PublicMintingNotAllowed,501 502 AddressNotInAllowlist,503504 505 CollectionNameLimitExceeded,506 507 CollectionDescriptionLimitExceeded,508 509 CollectionTokenPrefixLimitExceeded,510 511 TotalCollectionsLimitExceeded,512 513 CollectionAdminCountExceeded,514 515 CollectionLimitBoundsExceeded,516 517 OwnerPermissionsCantBeReverted,518 519 TransferNotAllowed,520 521 AccountTokenLimitExceeded,522 523 CollectionTokenLimitExceeded,524 525 MetadataFlagFrozen,526527 528 TokenNotFound,529 530 TokenValueTooLow,531 532 ApprovedValueTooLow,533 534 CantApproveMoreThanOwned,535536 537 AddressIsZero,538 539 UnsupportedOperation,540541 542 NotSufficientFounds,543544 545 UserIsNotAllowedToNest,546 547 SourceCollectionIsNotAllowedToNest,548549 550 CollectionFieldSizeExceeded,551552 553 NoSpaceForProperty,554555 556 PropertyLimitReached,557558 559 PropertyKeyIsTooLong,560561 562 InvalidCharacterInPropertyKey,563564 565 EmptyPropertyKey,566567 568 CollectionIsExternal,569570 571 CollectionIsInternal,572 }573574 575 #[pallet::storage]576 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;577578 579 #[pallet::storage]580 pub type DestroyedCollectionCount<T> =581 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;582583 584 #[pallet::storage]585 pub type CollectionById<T> = StorageMap<586 Hasher = Blake2_128Concat,587 Key = CollectionId,588 Value = Collection<<T as frame_system::Config>::AccountId>,589 QueryKind = OptionQuery,590 >;591592 593 #[pallet::storage]594 #[pallet::getter(fn collection_properties)]595 pub type CollectionProperties<T> = StorageMap<596 Hasher = Blake2_128Concat,597 Key = CollectionId,598 Value = Properties,599 QueryKind = ValueQuery,600 OnEmpty = up_data_structs::CollectionProperties,601 >;602603 604 #[pallet::storage]605 #[pallet::getter(fn property_permissions)]606 pub type CollectionPropertyPermissions<T> = StorageMap<607 Hasher = Blake2_128Concat,608 Key = CollectionId,609 Value = PropertiesPermissionMap,610 QueryKind = ValueQuery,611 >;612613 614 #[pallet::storage]615 pub type AdminAmount<T> = StorageMap<616 Hasher = Blake2_128Concat,617 Key = CollectionId,618 Value = u32,619 QueryKind = ValueQuery,620 >;621622 623 #[pallet::storage]624 pub type IsAdmin<T: Config> = StorageNMap<625 Key = (626 Key<Blake2_128Concat, CollectionId>,627 Key<Blake2_128Concat, T::CrossAccountId>,628 ),629 Value = bool,630 QueryKind = ValueQuery,631 >;632633 634 #[pallet::storage]635 pub type Allowlist<T: Config> = StorageNMap<636 Key = (637 Key<Blake2_128Concat, CollectionId>,638 Key<Blake2_128Concat, T::CrossAccountId>,639 ),640 Value = bool,641 QueryKind = ValueQuery,642 >;643644 645 #[pallet::storage]646 pub type DummyStorageValue<T: Config> = StorageValue<647 Value = (648 CollectionStats,649 CollectionId,650 TokenId,651 TokenChild,652 PhantomType<(653 TokenData<T::CrossAccountId>,654 RpcCollection<T::AccountId>,655 656 RmrkCollectionInfo<T::AccountId>,657 RmrkInstanceInfo<T::AccountId>,658 RmrkResourceInfo,659 RmrkPropertyInfo,660 RmrkBaseInfo<T::AccountId>,661 RmrkPartType,662 RmrkBoundedTheme,663 RmrkNftChild,664 )>,665 ),666 QueryKind = OptionQuery,667 >;668669 #[pallet::hooks]670 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {671 fn on_runtime_upgrade() -> Weight {672 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {673 use up_data_structs::{CollectionVersion1, CollectionVersion2};674 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {675 let mut props = Vec::new();676 if !v.offchain_schema.is_empty() {677 props.push(Property {678 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),679 value: v680 .offchain_schema681 .clone()682 .into_inner()683 .try_into()684 .expect("offchain schema too big"),685 });686 }687 if !v.variable_on_chain_schema.is_empty() {688 props.push(Property {689 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),690 value: v691 .variable_on_chain_schema692 .clone()693 .into_inner()694 .try_into()695 .expect("offchain schema too big"),696 });697 }698 if !v.const_on_chain_schema.is_empty() {699 props.push(Property {700 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),701 value: v702 .const_on_chain_schema703 .clone()704 .into_inner()705 .try_into()706 .expect("offchain schema too big"),707 });708 }709 props.push(Property {710 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),711 value: match v.schema_version {712 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),713 SchemaVersion::Unique => b"Unique".as_slice(),714 }715 .to_vec()716 .try_into()717 .unwrap(),718 });719 Self::set_scoped_collection_properties(720 id,721 PropertyScope::None,722 props.into_iter(),723 )724 .expect("existing data larger than properties");725 let mut new = CollectionVersion2::from(v.clone());726 new.permissions.access = Some(v.access);727 new.permissions.mint_mode = Some(v.mint_mode);728 Some(new)729 });730 }731732 0733 }734 }735}736737impl<T: Config> Pallet<T> {738 739 740 741 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {742 ensure!(743 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,744 <Error<T>>::AddressIsZero745 );746 Ok(())747 }748749 750 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {751 <IsAdmin<T>>::iter_prefix((collection,))752 .map(|(a, _)| a)753 .collect()754 }755756 757 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {758 <Allowlist<T>>::iter_prefix((collection,))759 .map(|(a, _)| a)760 .collect()761 }762763 764 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {765 <Allowlist<T>>::get((collection, user))766 }767768 769 pub fn collection_stats() -> CollectionStats {770 let created = <CreatedCollectionCount<T>>::get();771 let destroyed = <DestroyedCollectionCount<T>>::get();772 CollectionStats {773 created: created.0,774 destroyed: destroyed.0,775 alive: created.0 - destroyed.0,776 }777 }778779 780 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {781 let collection = <CollectionById<T>>::get(collection);782 if collection.is_none() {783 return None;784 }785786 let collection = collection.unwrap();787 let limits = collection.limits;788 let effective_limits = CollectionLimits {789 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),790 sponsored_data_size: Some(limits.sponsored_data_size()),791 sponsored_data_rate_limit: Some(792 limits793 .sponsored_data_rate_limit794 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),795 ),796 token_limit: Some(limits.token_limit()),797 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(798 match collection.mode {799 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,800 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,801 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,802 },803 )),804 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),805 owner_can_transfer: Some(limits.owner_can_transfer()),806 owner_can_destroy: Some(limits.owner_can_destroy()),807 transfers_enabled: Some(limits.transfers_enabled()),808 };809810 Some(effective_limits)811 }812813 814 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {815 let Collection {816 name,817 description,818 owner,819 mode,820 token_prefix,821 sponsorship,822 limits,823 permissions,824 external_collection,825 } = <CollectionById<T>>::get(collection)?;826827 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)828 .into_iter()829 .map(|(key, permission)| PropertyKeyPermission { key, permission })830 .collect();831832 let properties = <CollectionProperties<T>>::get(collection)833 .into_iter()834 .map(|(key, value)| Property { key, value })835 .collect();836837 let permissions = CollectionPermissions {838 access: Some(permissions.access()),839 mint_mode: Some(permissions.mint_mode()),840 nesting: Some(permissions.nesting().clone()),841 };842843 Some(RpcCollection {844 name: name.into_inner(),845 description: description.into_inner(),846 owner,847 mode,848 token_prefix: token_prefix.into_inner(),849 sponsorship,850 limits,851 permissions,852 token_property_permissions,853 properties,854 read_only: external_collection,855 })856 }857}858859macro_rules! limit_default {860 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{861 $(862 if let Some($new) = $new.$field {863 let $old = $old.$field($($arg)?);864 let _ = $new;865 let _ = $old;866 $check867 } else {868 $new.$field = $old.$field869 }870 )*871 }};872}873macro_rules! limit_default_clone {874 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{875 $(876 if let Some($new) = $new.$field.clone() {877 let $old = $old.$field($($arg)?);878 let _ = $new;879 let _ = $old;880 $check881 } else {882 $new.$field = $old.$field.clone()883 }884 )*885 }};886}887888impl<T: Config> Pallet<T> {889 890 891 892 893 894 pub fn init_collection(895 owner: T::CrossAccountId,896 data: CreateCollectionData<T::AccountId>,897 is_external: bool,898 ) -> Result<CollectionId, DispatchError> {899 {900 ensure!(901 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,902 Error::<T>::CollectionTokenPrefixLimitExceeded903 );904 }905906 let created_count = <CreatedCollectionCount<T>>::get()907 .0908 .checked_add(1)909 .ok_or(ArithmeticError::Overflow)?;910 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;911 let id = CollectionId(created_count);912913 914 ensure!(915 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,916 <Error<T>>::TotalCollectionsLimitExceeded917 );918919 920921 let collection = Collection {922 owner: owner.as_sub().clone(),923 name: data.name,924 mode: data.mode.clone(),925 description: data.description,926 token_prefix: data.token_prefix,927 sponsorship: data928 .pending_sponsor929 .map(SponsorshipState::Unconfirmed)930 .unwrap_or_default(),931 limits: data932 .limits933 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))934 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,935 permissions: data936 .permissions937 .map(|permissions| {938 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)939 })940 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,941 external_collection: is_external,942 };943944 let mut collection_properties = up_data_structs::CollectionProperties::get();945 collection_properties946 .try_set_from_iter(data.properties.into_iter())947 .map_err(<Error<T>>::from)?;948949 CollectionProperties::<T>::insert(id, collection_properties);950951 let mut token_props_permissions = PropertiesPermissionMap::new();952 token_props_permissions953 .try_set_from_iter(data.token_property_permissions.into_iter())954 .map_err(<Error<T>>::from)?;955956 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);957958 959 {960 let mut imbalance =961 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();962 imbalance.subsume(963 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(964 &T::TreasuryAccountId::get(),965 T::CollectionCreationPrice::get(),966 ),967 );968 <T as Config>::Currency::settle(969 owner.as_sub(),970 imbalance,971 WithdrawReasons::TRANSFER,972 ExistenceRequirement::KeepAlive,973 )974 .map_err(|_| Error::<T>::NotSufficientFounds)?;975 }976977 <CreatedCollectionCount<T>>::put(created_count);978 <Pallet<T>>::deposit_event(Event::CollectionCreated(979 id,980 data.mode.id(),981 owner.as_sub().clone(),982 ));983 <PalletEvm<T>>::deposit_log(984 erc::CollectionHelpersEvents::CollectionCreated {985 owner: *owner.as_eth(),986 collection_id: eth::collection_id_to_address(id),987 }988 .to_log(T::ContractAddress::get()),989 );990 <CollectionById<T>>::insert(id, collection);991 Ok(id)992 }993994 995 996 997 998 pub fn destroy_collection(999 collection: CollectionHandle<T>,1000 sender: &T::CrossAccountId,1001 ) -> DispatchResult {1002 ensure!(1003 collection.limits.owner_can_destroy(),1004 <Error<T>>::NoPermission,1005 );1006 collection.check_is_owner(sender)?;10071008 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1009 .01010 .checked_add(1)1011 .ok_or(ArithmeticError::Overflow)?;10121013 10141015 <DestroyedCollectionCount<T>>::put(destroyed_collections);1016 <CollectionById<T>>::remove(collection.id);1017 <AdminAmount<T>>::remove(collection.id);1018 <IsAdmin<T>>::remove_prefix((collection.id,), None);1019 <Allowlist<T>>::remove_prefix((collection.id,), None);1020 <CollectionProperties<T>>::remove(collection.id);10211022 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));1023 Ok(())1024 }10251026 1027 1028 1029 1030 1031 pub fn set_collection_property(1032 collection: &CollectionHandle<T>,1033 sender: &T::CrossAccountId,1034 property: Property,1035 ) -> DispatchResult {1036 collection.check_is_owner_or_admin(sender)?;10371038 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1039 let property = property.clone();1040 properties.try_set(property.key, property.value)1041 })1042 .map_err(<Error<T>>::from)?;10431044 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10451046 Ok(())1047 }10481049 1050 1051 1052 1053 1054 pub fn set_scoped_collection_property(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 property: Property,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1060 properties.try_scoped_set(scope, property.key, property.value)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 1068 1069 1070 1071 1072 pub fn set_scoped_collection_properties(1073 collection_id: CollectionId,1074 scope: PropertyScope,1075 properties: impl Iterator<Item = Property>,1076 ) -> DispatchResult {1077 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1078 stored_properties.try_scoped_set_from_iter(scope, properties)1079 })1080 .map_err(<Error<T>>::from)?;10811082 Ok(())1083 }10841085 1086 1087 1088 1089 1090 #[transactional]1091 pub fn set_collection_properties(1092 collection: &CollectionHandle<T>,1093 sender: &T::CrossAccountId,1094 properties: Vec<Property>,1095 ) -> DispatchResult {1096 for property in properties {1097 Self::set_collection_property(collection, sender, property)?;1098 }10991100 Ok(())1101 }11021103 1104 1105 1106 1107 1108 pub fn delete_collection_property(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 property_key: PropertyKey,1112 ) -> DispatchResult {1113 collection.check_is_owner_or_admin(sender)?;11141115 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1116 properties.remove(&property_key)1117 })1118 .map_err(<Error<T>>::from)?;11191120 Self::deposit_event(Event::CollectionPropertyDeleted(1121 collection.id,1122 property_key,1123 ));11241125 Ok(())1126 }11271128 1129 1130 1131 1132 1133 #[transactional]1134 pub fn delete_collection_properties(1135 collection: &CollectionHandle<T>,1136 sender: &T::CrossAccountId,1137 property_keys: Vec<PropertyKey>,1138 ) -> DispatchResult {1139 for key in property_keys {1140 Self::delete_collection_property(collection, sender, key)?;1141 }11421143 Ok(())1144 }11451146 1147 1148 1149 1150 1151 1152 pub fn set_property_permission_unchecked(1153 collection: CollectionId,1154 property_permission: PropertyKeyPermission,1155 ) -> DispatchResult {1156 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1157 permissions.try_set(property_permission.key, property_permission.permission)1158 })1159 .map_err(<Error<T>>::from)?;1160 Ok(())1161 }11621163 1164 1165 1166 1167 1168 pub fn set_property_permission(1169 collection: &CollectionHandle<T>,1170 sender: &T::CrossAccountId,1171 property_permission: PropertyKeyPermission,1172 ) -> DispatchResult {1173 collection.check_is_owner_or_admin(sender)?;11741175 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1176 let current_permission = all_permissions.get(&property_permission.key);1177 if matches![1178 current_permission,1179 Some(PropertyPermission { mutable: false, .. })1180 ] {1181 return Err(<Error<T>>::NoPermission.into());1182 }11831184 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1185 let property_permission = property_permission.clone();1186 permissions.try_set(property_permission.key, property_permission.permission)1187 })1188 .map_err(<Error<T>>::from)?;11891190 Self::deposit_event(Event::PropertyPermissionSet(1191 collection.id,1192 property_permission.key,1193 ));11941195 Ok(())1196 }11971198 1199 1200 1201 1202 1203 #[transactional]1204 pub fn set_token_property_permissions(1205 collection: &CollectionHandle<T>,1206 sender: &T::CrossAccountId,1207 property_permissions: Vec<PropertyKeyPermission>,1208 ) -> DispatchResult {1209 for prop_pemission in property_permissions {1210 Self::set_property_permission(collection, sender, prop_pemission)?;1211 }12121213 Ok(())1214 }12151216 1217 pub fn get_collection_property(1218 collection_id: CollectionId,1219 key: &PropertyKey,1220 ) -> Option<PropertyValue> {1221 Self::collection_properties(collection_id).get(key).cloned()1222 }12231224 1225 pub fn bytes_keys_to_property_keys(1226 keys: Vec<Vec<u8>>,1227 ) -> Result<Vec<PropertyKey>, DispatchError> {1228 keys.into_iter()1229 .map(|key| -> Result<PropertyKey, DispatchError> {1230 key.try_into()1231 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1232 })1233 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1234 }12351236 1237 pub fn filter_collection_properties(1238 collection_id: CollectionId,1239 keys: Option<Vec<PropertyKey>>,1240 ) -> Result<Vec<Property>, DispatchError> {1241 let properties = Self::collection_properties(collection_id);12421243 let properties = keys1244 .map(|keys| {1245 keys.into_iter()1246 .filter_map(|key| {1247 properties.get(&key).map(|value| Property {1248 key,1249 value: value.clone(),1250 })1251 })1252 .collect()1253 })1254 .unwrap_or_else(|| {1255 properties1256 .into_iter()1257 .map(|(key, value)| Property { key, value })1258 .collect()1259 });12601261 Ok(properties)1262 }12631264 1265 pub fn filter_property_permissions(1266 collection_id: CollectionId,1267 keys: Option<Vec<PropertyKey>>,1268 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1269 let permissions = Self::property_permissions(collection_id);12701271 let key_permissions = keys1272 .map(|keys| {1273 keys.into_iter()1274 .filter_map(|key| {1275 permissions1276 .get(&key)1277 .map(|permission| PropertyKeyPermission {1278 key,1279 permission: permission.clone(),1280 })1281 })1282 .collect()1283 })1284 .unwrap_or_else(|| {1285 permissions1286 .into_iter()1287 .map(|(key, permission)| PropertyKeyPermission { key, permission })1288 .collect()1289 });12901291 Ok(key_permissions)1292 }12931294 1295 pub fn toggle_allowlist(1296 collection: &CollectionHandle<T>,1297 sender: &T::CrossAccountId,1298 user: &T::CrossAccountId,1299 allowed: bool,1300 ) -> DispatchResult {1301 collection.check_is_owner_or_admin(sender)?;13021303 13041305 if allowed {1306 <Allowlist<T>>::insert((collection.id, user), true);1307 } else {1308 <Allowlist<T>>::remove((collection.id, user));1309 }13101311 Ok(())1312 }13131314 1315 pub fn toggle_admin(1316 collection: &CollectionHandle<T>,1317 sender: &T::CrossAccountId,1318 user: &T::CrossAccountId,1319 admin: bool,1320 ) -> DispatchResult {1321 collection.check_is_owner(sender)?;13221323 let was_admin = <IsAdmin<T>>::get((collection.id, user));1324 if was_admin == admin {1325 return Ok(());1326 }1327 let amount = <AdminAmount<T>>::get(collection.id);13281329 if admin {1330 let amount = amount1331 .checked_add(1)1332 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1333 ensure!(1334 amount <= Self::collection_admins_limit(),1335 <Error<T>>::CollectionAdminCountExceeded,1336 );13371338 13391340 <AdminAmount<T>>::insert(collection.id, amount);1341 <IsAdmin<T>>::insert((collection.id, user), true);1342 } else {1343 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1344 <IsAdmin<T>>::remove((collection.id, user));1345 }13461347 Ok(())1348 }13491350 1351 pub fn clamp_limits(1352 mode: CollectionMode,1353 old_limit: &CollectionLimits,1354 mut new_limit: CollectionLimits,1355 ) -> Result<CollectionLimits, DispatchError> {1356 let limits = old_limit;1357 limit_default!(old_limit, new_limit,1358 account_token_ownership_limit => ensure!(1359 new_limit <= MAX_TOKEN_OWNERSHIP,1360 <Error<T>>::CollectionLimitBoundsExceeded,1361 ),1362 sponsored_data_size => ensure!(1363 new_limit <= CUSTOM_DATA_LIMIT,1364 <Error<T>>::CollectionLimitBoundsExceeded,1365 ),13661367 sponsored_data_rate_limit => {},1368 token_limit => ensure!(1369 old_limit >= new_limit && new_limit > 0,1370 <Error<T>>::CollectionTokenLimitExceeded1371 ),13721373 sponsor_transfer_timeout(match mode {1374 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1375 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1376 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1377 }) => ensure!(1378 new_limit <= MAX_SPONSOR_TIMEOUT,1379 <Error<T>>::CollectionLimitBoundsExceeded,1380 ),1381 sponsor_approve_timeout => {},1382 owner_can_transfer => ensure!(1383 !limits.owner_can_transfer_instaled() ||1384 old_limit || !new_limit,1385 <Error<T>>::OwnerPermissionsCantBeReverted,1386 ),1387 owner_can_destroy => ensure!(1388 old_limit || !new_limit,1389 <Error<T>>::OwnerPermissionsCantBeReverted,1390 ),1391 transfers_enabled => {},1392 );1393 Ok(new_limit)1394 }13951396 1397 pub fn clamp_permissions(1398 _mode: CollectionMode,1399 old_permission: &CollectionPermissions,1400 mut new_permission: CollectionPermissions,1401 ) -> Result<CollectionPermissions, DispatchError> {1402 limit_default_clone!(old_permission, new_permission,1403 access => {},1404 mint_mode => {},1405 nesting => { },1406 );1407 Ok(new_permission)1408 }1409}141014111412#[macro_export]1413macro_rules! unsupported {1414 () => {1415 Err(<Error<T>>::UnsupportedOperation.into())1416 };1417}141814191420pub trait CommonWeightInfo<CrossAccountId> {1421 1422 fn create_item() -> Weight;14231424 1425 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14261427 1428 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14291430 1431 fn burn_item() -> Weight;14321433 1434 1435 1436 fn set_collection_properties(amount: u32) -> Weight;14371438 1439 1440 1441 fn delete_collection_properties(amount: u32) -> Weight;14421443 1444 1445 1446 fn set_token_properties(amount: u32) -> Weight;14471448 1449 1450 1451 fn delete_token_properties(amount: u32) -> Weight;14521453 1454 1455 1456 fn set_token_property_permissions(amount: u32) -> Weight;14571458 1459 fn transfer() -> Weight;14601461 1462 fn approve() -> Weight;14631464 1465 fn transfer_from() -> Weight;14661467 1468 fn burn_from() -> Weight;14691470 1471 1472 1473 1474 fn burn_recursively_self_raw() -> Weight;14751476 1477 1478 1479 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14801481 1482 1483 1484 1485 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1486 Self::burn_recursively_self_raw()1487 .saturating_mul(max_selfs.max(1) as u64)1488 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1489 }1490}149114921493pub trait RefungibleExtensionsWeightInfo {1494 1495 fn repartition() -> Weight;1496}149714981499150015011502pub trait CommonCollectionOperations<T: Config> {1503 1504 1505 1506 1507 1508 1509 fn create_item(1510 &self,1511 sender: T::CrossAccountId,1512 to: T::CrossAccountId,1513 data: CreateItemData,1514 nesting_budget: &dyn Budget,1515 ) -> DispatchResultWithPostInfo;15161517 1518 1519 1520 1521 1522 1523 fn create_multiple_items(1524 &self,1525 sender: T::CrossAccountId,1526 to: T::CrossAccountId,1527 data: Vec<CreateItemData>,1528 nesting_budget: &dyn Budget,1529 ) -> DispatchResultWithPostInfo;15301531 1532 1533 1534 1535 1536 1537 fn create_multiple_items_ex(1538 &self,1539 sender: T::CrossAccountId,1540 data: CreateItemExData<T::CrossAccountId>,1541 nesting_budget: &dyn Budget,1542 ) -> DispatchResultWithPostInfo;15431544 1545 1546 1547 1548 1549 fn burn_item(1550 &self,1551 sender: T::CrossAccountId,1552 token: TokenId,1553 amount: u128,1554 ) -> DispatchResultWithPostInfo;15551556 1557 1558 1559 1560 1561 1562 fn burn_item_recursively(1563 &self,1564 sender: T::CrossAccountId,1565 token: TokenId,1566 self_budget: &dyn Budget,1567 breadth_budget: &dyn Budget,1568 ) -> DispatchResultWithPostInfo;15691570 1571 1572 1573 1574 fn set_collection_properties(1575 &self,1576 sender: T::CrossAccountId,1577 properties: Vec<Property>,1578 ) -> DispatchResultWithPostInfo;15791580 1581 1582 1583 1584 fn delete_collection_properties(1585 &self,1586 sender: &T::CrossAccountId,1587 property_keys: Vec<PropertyKey>,1588 ) -> DispatchResultWithPostInfo;15891590 1591 1592 1593 1594 1595 1596 1597 1598 1599 fn set_token_properties(1600 &self,1601 sender: T::CrossAccountId,1602 token_id: TokenId,1603 properties: Vec<Property>,1604 budget: &dyn Budget,1605 ) -> DispatchResultWithPostInfo;16061607 1608 1609 1610 1611 1612 1613 1614 1615 1616 fn delete_token_properties(1617 &self,1618 sender: T::CrossAccountId,1619 token_id: TokenId,1620 property_keys: Vec<PropertyKey>,1621 budget: &dyn Budget,1622 ) -> DispatchResultWithPostInfo;16231624 1625 1626 1627 1628 1629 1630 fn set_token_property_permissions(1631 &self,1632 sender: &T::CrossAccountId,1633 property_permissions: Vec<PropertyKeyPermission>,1634 ) -> DispatchResultWithPostInfo;16351636 1637 1638 1639 1640 1641 1642 1643 fn transfer(1644 &self,1645 sender: T::CrossAccountId,1646 to: T::CrossAccountId,1647 token: TokenId,1648 amount: u128,1649 budget: &dyn Budget,1650 ) -> DispatchResultWithPostInfo;16511652 1653 1654 1655 1656 1657 1658 fn approve(1659 &self,1660 sender: T::CrossAccountId,1661 spender: T::CrossAccountId,1662 token: TokenId,1663 amount: u128,1664 ) -> DispatchResultWithPostInfo;16651666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 fn transfer_from(1677 &self,1678 sender: T::CrossAccountId,1679 from: T::CrossAccountId,1680 to: T::CrossAccountId,1681 token: TokenId,1682 amount: u128,1683 budget: &dyn Budget,1684 ) -> DispatchResultWithPostInfo;16851686 1687 1688 1689 1690 1691 1692 1693 1694 1695 fn burn_from(1696 &self,1697 sender: T::CrossAccountId,1698 from: T::CrossAccountId,1699 token: TokenId,1700 amount: u128,1701 budget: &dyn Budget,1702 ) -> DispatchResultWithPostInfo;17031704 1705 1706 1707 1708 1709 1710 fn check_nesting(1711 &self,1712 sender: T::CrossAccountId,1713 from: (CollectionId, TokenId),1714 under: TokenId,1715 budget: &dyn Budget,1716 ) -> DispatchResult;17171718 1719 1720 1721 1722 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17231724 1725 1726 1727 1728 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17291730 1731 1732 1733 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17341735 1736 fn collection_tokens(&self) -> Vec<TokenId>;17371738 1739 1740 1741 fn token_exists(&self, token: TokenId) -> bool;17421743 1744 fn last_token_id(&self) -> TokenId;17451746 1747 1748 1749 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17501751 1752 1753 1754 1755 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17561757 1758 1759 1760 1761 1762 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17631764 1765 fn total_supply(&self) -> u32;17661767 1768 1769 1770 fn account_balance(&self, account: T::CrossAccountId) -> u32;17711772 1773 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17741775 1776 fn total_pieces(&self, token: TokenId) -> Option<u128>;17771778 1779 1780 1781 1782 1783 fn allowance(1784 &self,1785 sender: T::CrossAccountId,1786 spender: T::CrossAccountId,1787 token: TokenId,1788 ) -> u128;17891790 1791 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1792}179317941795pub trait RefungibleExtensions<T>1796where1797 T: Config,1798{1799 1800 1801 1802 1803 1804 1805 1806 fn repartition(1807 &self,1808 sender: &T::CrossAccountId,1809 token: TokenId,1810 amount: u128,1811 ) -> DispatchResultWithPostInfo;1812}18131814181518161817pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1818 let post_info = PostDispatchInfo {1819 actual_weight: Some(weight),1820 pays_fee: Pays::Yes,1821 };1822 match res {1823 Ok(()) => Ok(post_info),1824 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1825 }1826}18271828impl<T: Config> From<PropertiesError> for Error<T> {1829 fn from(error: PropertiesError) -> Self {1830 match error {1831 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1832 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1833 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1834 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1835 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1836 }1837 }1838}