123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#![warn(missing_docs)]55#![cfg_attr(not(feature = "std"), no_std)]56extern crate alloc;5758use core::ops::{Deref, DerefMut};59use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};60use sp_std::vec::Vec;61use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};62use evm_coder::ToLog;63use frame_support::{64 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},65 ensure,66 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},67 weights::Pays,68 transactional,69};70use pallet_evm::GasWeightMapping;71use up_data_structs::{72 COLLECTION_NUMBER_LIMIT,73 Collection,74 RpcCollection,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117 SchemaVersion,118};119120pub use pallet::*;121use sp_core::H160;122use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod dispatch;126pub mod erc;127pub mod eth;128pub mod weights;129130131pub type SelfWeightOf<T> = <T as Config>::WeightInfo;132133134135#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]136pub struct CollectionHandle<T: Config> {137 138 pub id: CollectionId,139 collection: Collection<T::AccountId>,140 141 pub recorder: SubstrateRecorder<T>,142}143144impl<T: Config> WithRecorder<T> for CollectionHandle<T> {145 fn recorder(&self) -> &SubstrateRecorder<T> {146 &self.recorder147 }148 fn into_recorder(self) -> SubstrateRecorder<T> {149 self.recorder150 }151}152153impl<T: Config> CollectionHandle<T> {154 155 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {156 <CollectionById<T>>::get(id).map(|collection| Self {157 id,158 collection,159 recorder: SubstrateRecorder::new(gas_limit),160 })161 }162163 164 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {165 <CollectionById<T>>::get(id).map(|collection| Self {166 id,167 collection,168 recorder,169 })170 }171172 173 174 pub fn new(id: CollectionId) -> Option<Self> {175 Self::new_with_gas_limit(id, u64::MAX)176 }177178 179 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {180 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)181 }182183 184 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {185 self.recorder186 .consume_gas(T::GasWeightMapping::weight_to_gas(187 <T as frame_system::Config>::DbWeight::get()188 .read189 .saturating_mul(reads),190 ))191 }192193 194 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {195 self.recorder196 .consume_gas(T::GasWeightMapping::weight_to_gas(197 <T as frame_system::Config>::DbWeight::get()198 .write199 .saturating_mul(writes),200 ))201 }202203 204 pub fn save(self) -> DispatchResult {205 <CollectionById<T>>::insert(self.id, self.collection);206 Ok(())207 }208209 210 211 212 213 214 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {215 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);216 Ok(())217 }218219 220 221 222 223 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {224 if self.collection.sponsorship.pending_sponsor() != Some(sender) {225 return Ok(false);226 }227228 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());229 Ok(true)230 }231232 233 234 pub fn check_is_internal(&self) -> DispatchResult {235 if self.external_collection {236 return Err(<Error<T>>::CollectionIsExternal)?;237 }238239 Ok(())240 }241242 243 244 pub fn check_is_external(&self) -> DispatchResult {245 if !self.external_collection {246 return Err(<Error<T>>::CollectionIsInternal)?;247 }248249 Ok(())250 }251}252253impl<T: Config> Deref for CollectionHandle<T> {254 type Target = Collection<T::AccountId>;255256 fn deref(&self) -> &Self::Target {257 &self.collection258 }259}260261impl<T: Config> DerefMut for CollectionHandle<T> {262 fn deref_mut(&mut self) -> &mut Self::Target {263 &mut self.collection264 }265}266267impl<T: Config> CollectionHandle<T> {268 269 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {270 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);271 Ok(())272 }273274 275 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {276 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))277 }278279 280 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {281 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);282 Ok(())283 }284285 286 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {287 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)288 }289290 291 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {292 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)293 }294295 296 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {297 ensure!(298 <Allowlist<T>>::get((self.id, user)),299 <Error<T>>::AddressNotInAllowlist300 );301 Ok(())302 }303}304305#[frame_support::pallet]306pub mod pallet {307 use super::*;308 use pallet_evm::account;309 use dispatch::CollectionDispatch;310 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};311 use frame_system::pallet_prelude::*;312 use frame_support::traits::Currency;313 use up_data_structs::{TokenId, mapping::TokenAddressMapping};314 use scale_info::TypeInfo;315 use weights::WeightInfo;316317 #[pallet::config]318 pub trait Config:319 frame_system::Config320 + pallet_evm_coder_substrate::Config321 + pallet_evm::Config322 + TypeInfo323 + account::Config324 {325 326 type WeightInfo: WeightInfo;327328 329 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;330331 332 type Currency: Currency<Self::AccountId>;333334 335 #[pallet::constant]336 type CollectionCreationPrice: Get<337 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,338 >;339340 341 type CollectionDispatch: CollectionDispatch<Self>;342343 344 type TreasuryAccountId: Get<Self::AccountId>;345346 347 type ContractAddress: Get<H160>;348349 350 type EvmTokenAddressMapping: TokenAddressMapping<H160>;351352 353 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;354 }355356 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);357358 #[pallet::pallet]359 #[pallet::storage_version(STORAGE_VERSION)]360 #[pallet::generate_store(pub(super) trait Store)]361 pub struct Pallet<T>(_);362363 #[pallet::extra_constants]364 impl<T: Config> Pallet<T> {365 366 pub fn collection_admins_limit() -> u32 {367 COLLECTION_ADMINS_LIMIT368 }369 }370371 #[pallet::event]372 #[pallet::generate_deposit(pub fn deposit_event)]373 pub enum Event<T: Config> {374 375 CollectionCreated(376 377 CollectionId,378 379 u8,380 381 T::AccountId,382 ),383384 385 CollectionDestroyed(386 387 CollectionId,388 ),389390 391 ItemCreated(392 393 CollectionId,394 395 TokenId,396 397 T::CrossAccountId,398 399 u128,400 ),401402 403 ItemDestroyed(404 405 CollectionId,406 407 TokenId,408 409 T::CrossAccountId,410 411 u128,412 ),413414 415 Transfer(416 417 CollectionId,418 419 TokenId,420 421 T::CrossAccountId,422 423 T::CrossAccountId,424 425 u128,426 ),427428 429 Approved(430 431 CollectionId,432 433 TokenId,434 435 T::CrossAccountId,436 437 T::CrossAccountId,438 439 u128,440 ),441442 443 CollectionPropertySet(444 445 CollectionId,446 447 PropertyKey,448 ),449450 451 CollectionPropertyDeleted(452 453 CollectionId,454 455 PropertyKey,456 ),457458 459 TokenPropertySet(460 461 CollectionId,462 463 TokenId,464 465 PropertyKey,466 ),467468 469 TokenPropertyDeleted(470 471 CollectionId,472 473 TokenId,474 475 PropertyKey,476 ),477478 479 PropertyPermissionSet(480 481 CollectionId,482 483 PropertyKey,484 ),485 }486487 #[pallet::error]488 pub enum Error<T> {489 490 CollectionNotFound,491 492 MustBeTokenOwner,493 494 NoPermission,495 496 CantDestroyNotEmptyCollection,497 498 PublicMintingNotAllowed,499 500 AddressNotInAllowlist,501502 503 CollectionNameLimitExceeded,504 505 CollectionDescriptionLimitExceeded,506 507 CollectionTokenPrefixLimitExceeded,508 509 TotalCollectionsLimitExceeded,510 511 CollectionAdminCountExceeded,512 513 CollectionLimitBoundsExceeded,514 515 OwnerPermissionsCantBeReverted,516 517 TransferNotAllowed,518 519 AccountTokenLimitExceeded,520 521 CollectionTokenLimitExceeded,522 523 MetadataFlagFrozen,524525 526 TokenNotFound,527 528 TokenValueTooLow,529 530 ApprovedValueTooLow,531 532 CantApproveMoreThanOwned,533534 535 AddressIsZero,536 537 UnsupportedOperation,538539 540 NotSufficientFounds,541542 543 UserIsNotAllowedToNest,544 545 SourceCollectionIsNotAllowedToNest,546547 548 CollectionFieldSizeExceeded,549550 551 NoSpaceForProperty,552553 554 PropertyLimitReached,555556 557 PropertyKeyIsTooLong,558559 560 InvalidCharacterInPropertyKey,561562 563 EmptyPropertyKey,564565 566 CollectionIsExternal,567568 569 CollectionIsInternal,570 }571572 573 #[pallet::storage]574 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;575576 577 #[pallet::storage]578 pub type DestroyedCollectionCount<T> =579 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;580581 582 #[pallet::storage]583 pub type CollectionById<T> = StorageMap<584 Hasher = Blake2_128Concat,585 Key = CollectionId,586 Value = Collection<<T as frame_system::Config>::AccountId>,587 QueryKind = OptionQuery,588 >;589590 591 #[pallet::storage]592 #[pallet::getter(fn collection_properties)]593 pub type CollectionProperties<T> = StorageMap<594 Hasher = Blake2_128Concat,595 Key = CollectionId,596 Value = Properties,597 QueryKind = ValueQuery,598 OnEmpty = up_data_structs::CollectionProperties,599 >;600601 602 #[pallet::storage]603 #[pallet::getter(fn property_permissions)]604 pub type CollectionPropertyPermissions<T> = StorageMap<605 Hasher = Blake2_128Concat,606 Key = CollectionId,607 Value = PropertiesPermissionMap,608 QueryKind = ValueQuery,609 >;610611 612 #[pallet::storage]613 pub type AdminAmount<T> = StorageMap<614 Hasher = Blake2_128Concat,615 Key = CollectionId,616 Value = u32,617 QueryKind = ValueQuery,618 >;619620 621 #[pallet::storage]622 pub type IsAdmin<T: Config> = StorageNMap<623 Key = (624 Key<Blake2_128Concat, CollectionId>,625 Key<Blake2_128Concat, T::CrossAccountId>,626 ),627 Value = bool,628 QueryKind = ValueQuery,629 >;630631 632 #[pallet::storage]633 pub type Allowlist<T: Config> = StorageNMap<634 Key = (635 Key<Blake2_128Concat, CollectionId>,636 Key<Blake2_128Concat, T::CrossAccountId>,637 ),638 Value = bool,639 QueryKind = ValueQuery,640 >;641642 643 #[pallet::storage]644 pub type DummyStorageValue<T: Config> = StorageValue<645 Value = (646 CollectionStats,647 CollectionId,648 TokenId,649 TokenChild,650 PhantomType<(651 TokenData<T::CrossAccountId>,652 RpcCollection<T::AccountId>,653 654 RmrkCollectionInfo<T::AccountId>,655 RmrkInstanceInfo<T::AccountId>,656 RmrkResourceInfo,657 RmrkPropertyInfo,658 RmrkBaseInfo<T::AccountId>,659 RmrkPartType,660 RmrkBoundedTheme,661 RmrkNftChild,662 )>,663 ),664 QueryKind = OptionQuery,665 >;666667 #[pallet::hooks]668 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {669 fn on_runtime_upgrade() -> Weight {670 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {671 use up_data_structs::{CollectionVersion1, CollectionVersion2};672 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {673 let mut props = Vec::new();674 if !v.offchain_schema.is_empty() {675 props.push(Property {676 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),677 value: v678 .offchain_schema679 .clone()680 .into_inner()681 .try_into()682 .expect("offchain schema too big"),683 });684 }685 if !v.variable_on_chain_schema.is_empty() {686 props.push(Property {687 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),688 value: v689 .variable_on_chain_schema690 .clone()691 .into_inner()692 .try_into()693 .expect("offchain schema too big"),694 });695 }696 if !v.const_on_chain_schema.is_empty() {697 props.push(Property {698 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),699 value: v700 .const_on_chain_schema701 .clone()702 .into_inner()703 .try_into()704 .expect("offchain schema too big"),705 });706 }707 props.push(Property {708 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),709 value: match v.schema_version {710 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),711 SchemaVersion::Unique => b"Unique".as_slice(),712 }713 .to_vec()714 .try_into()715 .unwrap(),716 });717 Self::set_scoped_collection_properties(718 id,719 PropertyScope::None,720 props.into_iter(),721 )722 .expect("existing data larger than properties");723 let mut new = CollectionVersion2::from(v.clone());724 new.permissions.access = Some(v.access);725 new.permissions.mint_mode = Some(v.mint_mode);726 Some(new)727 });728 }729730 0731 }732 }733}734735impl<T: Config> Pallet<T> {736 737 738 739 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {740 ensure!(741 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,742 <Error<T>>::AddressIsZero743 );744 Ok(())745 }746747 748 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {749 <IsAdmin<T>>::iter_prefix((collection,))750 .map(|(a, _)| a)751 .collect()752 }753754 755 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {756 <Allowlist<T>>::iter_prefix((collection,))757 .map(|(a, _)| a)758 .collect()759 }760761 762 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {763 <Allowlist<T>>::get((collection, user))764 }765766 767 pub fn collection_stats() -> CollectionStats {768 let created = <CreatedCollectionCount<T>>::get();769 let destroyed = <DestroyedCollectionCount<T>>::get();770 CollectionStats {771 created: created.0,772 destroyed: destroyed.0,773 alive: created.0 - destroyed.0,774 }775 }776777 778 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {779 let collection = <CollectionById<T>>::get(collection);780 if collection.is_none() {781 return None;782 }783784 let collection = collection.unwrap();785 let limits = collection.limits;786 let effective_limits = CollectionLimits {787 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),788 sponsored_data_size: Some(limits.sponsored_data_size()),789 sponsored_data_rate_limit: Some(790 limits791 .sponsored_data_rate_limit792 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),793 ),794 token_limit: Some(limits.token_limit()),795 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(796 match collection.mode {797 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,798 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,799 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,800 },801 )),802 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),803 owner_can_transfer: Some(limits.owner_can_transfer()),804 owner_can_destroy: Some(limits.owner_can_destroy()),805 transfers_enabled: Some(limits.transfers_enabled()),806 };807808 Some(effective_limits)809 }810811 812 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {813 let Collection {814 name,815 description,816 owner,817 mode,818 token_prefix,819 sponsorship,820 limits,821 permissions,822 external_collection,823 } = <CollectionById<T>>::get(collection)?;824825 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)826 .into_iter()827 .map(|(key, permission)| PropertyKeyPermission { key, permission })828 .collect();829830 let properties = <CollectionProperties<T>>::get(collection)831 .into_iter()832 .map(|(key, value)| Property { key, value })833 .collect();834835 let permissions = CollectionPermissions {836 access: Some(permissions.access()),837 mint_mode: Some(permissions.mint_mode()),838 nesting: Some(permissions.nesting().clone()),839 };840841 Some(RpcCollection {842 name: name.into_inner(),843 description: description.into_inner(),844 owner,845 mode,846 token_prefix: token_prefix.into_inner(),847 sponsorship,848 limits,849 permissions,850 token_property_permissions,851 properties,852 read_only: external_collection,853 })854 }855}856857macro_rules! limit_default {858 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{859 $(860 if let Some($new) = $new.$field {861 let $old = $old.$field($($arg)?);862 let _ = $new;863 let _ = $old;864 $check865 } else {866 $new.$field = $old.$field867 }868 )*869 }};870}871macro_rules! limit_default_clone {872 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{873 $(874 if let Some($new) = $new.$field.clone() {875 let $old = $old.$field($($arg)?);876 let _ = $new;877 let _ = $old;878 $check879 } else {880 $new.$field = $old.$field.clone()881 }882 )*883 }};884}885886impl<T: Config> Pallet<T> {887 888 889 890 891 892 pub fn init_collection(893 owner: T::CrossAccountId,894 data: CreateCollectionData<T::AccountId>,895 is_external: bool,896 ) -> Result<CollectionId, DispatchError> {897 {898 ensure!(899 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,900 Error::<T>::CollectionTokenPrefixLimitExceeded901 );902 }903904 let created_count = <CreatedCollectionCount<T>>::get()905 .0906 .checked_add(1)907 .ok_or(ArithmeticError::Overflow)?;908 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;909 let id = CollectionId(created_count);910911 912 ensure!(913 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,914 <Error<T>>::TotalCollectionsLimitExceeded915 );916917 918919 let collection = Collection {920 owner: owner.as_sub().clone(),921 name: data.name,922 mode: data.mode.clone(),923 description: data.description,924 token_prefix: data.token_prefix,925 sponsorship: data926 .pending_sponsor927 .map(SponsorshipState::Unconfirmed)928 .unwrap_or_default(),929 limits: data930 .limits931 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))932 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,933 permissions: data934 .permissions935 .map(|permissions| {936 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)937 })938 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,939 external_collection: is_external,940 };941942 let mut collection_properties = up_data_structs::CollectionProperties::get();943 collection_properties944 .try_set_from_iter(data.properties.into_iter())945 .map_err(<Error<T>>::from)?;946947 CollectionProperties::<T>::insert(id, collection_properties);948949 let mut token_props_permissions = PropertiesPermissionMap::new();950 token_props_permissions951 .try_set_from_iter(data.token_property_permissions.into_iter())952 .map_err(<Error<T>>::from)?;953954 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);955956 957 {958 let mut imbalance =959 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();960 imbalance.subsume(961 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(962 &T::TreasuryAccountId::get(),963 T::CollectionCreationPrice::get(),964 ),965 );966 <T as Config>::Currency::settle(967 &owner.as_sub(),968 imbalance,969 WithdrawReasons::TRANSFER,970 ExistenceRequirement::KeepAlive,971 )972 .map_err(|_| Error::<T>::NotSufficientFounds)?;973 }974975 <CreatedCollectionCount<T>>::put(created_count);976 <Pallet<T>>::deposit_event(Event::CollectionCreated(977 id,978 data.mode.id(),979 owner.as_sub().clone(),980 ));981 <PalletEvm<T>>::deposit_log(982 erc::CollectionHelpersEvents::CollectionCreated {983 owner: *owner.as_eth(),984 collection_id: eth::collection_id_to_address(id),985 }986 .to_log(T::ContractAddress::get()),987 );988 <CollectionById<T>>::insert(id, collection);989 Ok(id)990 }991992 993 994 995 996 pub fn destroy_collection(997 collection: CollectionHandle<T>,998 sender: &T::CrossAccountId,999 ) -> DispatchResult {1000 ensure!(1001 collection.limits.owner_can_destroy(),1002 <Error<T>>::NoPermission,1003 );1004 collection.check_is_owner(sender)?;10051006 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1007 .01008 .checked_add(1)1009 .ok_or(ArithmeticError::Overflow)?;10101011 10121013 <DestroyedCollectionCount<T>>::put(destroyed_collections);1014 <CollectionById<T>>::remove(collection.id);1015 <AdminAmount<T>>::remove(collection.id);1016 <IsAdmin<T>>::remove_prefix((collection.id,), None);1017 <Allowlist<T>>::remove_prefix((collection.id,), None);1018 <CollectionProperties<T>>::remove(collection.id);10191020 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));1021 Ok(())1022 }10231024 1025 1026 1027 1028 1029 pub fn set_collection_property(1030 collection: &CollectionHandle<T>,1031 sender: &T::CrossAccountId,1032 property: Property,1033 ) -> DispatchResult {1034 collection.check_is_owner_or_admin(sender)?;10351036 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1037 let property = property.clone();1038 properties.try_set(property.key, property.value)1039 })1040 .map_err(<Error<T>>::from)?;10411042 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10431044 Ok(())1045 }10461047 1048 1049 1050 1051 1052 pub fn set_scoped_collection_property(1053 collection_id: CollectionId,1054 scope: PropertyScope,1055 property: Property,1056 ) -> DispatchResult {1057 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1058 properties.try_scoped_set(scope, property.key, property.value)1059 })1060 .map_err(<Error<T>>::from)?;10611062 Ok(())1063 }10641065 1066 1067 1068 1069 1070 pub fn set_scoped_collection_properties(1071 collection_id: CollectionId,1072 scope: PropertyScope,1073 properties: impl Iterator<Item = Property>,1074 ) -> DispatchResult {1075 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1076 stored_properties.try_scoped_set_from_iter(scope, properties)1077 })1078 .map_err(<Error<T>>::from)?;10791080 Ok(())1081 }10821083 1084 1085 1086 1087 1088 #[transactional]1089 pub fn set_collection_properties(1090 collection: &CollectionHandle<T>,1091 sender: &T::CrossAccountId,1092 properties: Vec<Property>,1093 ) -> DispatchResult {1094 for property in properties {1095 Self::set_collection_property(collection, sender, property)?;1096 }10971098 Ok(())1099 }11001101 1102 1103 1104 1105 1106 pub fn delete_collection_property(1107 collection: &CollectionHandle<T>,1108 sender: &T::CrossAccountId,1109 property_key: PropertyKey,1110 ) -> DispatchResult {1111 collection.check_is_owner_or_admin(sender)?;11121113 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1114 properties.remove(&property_key)1115 })1116 .map_err(<Error<T>>::from)?;11171118 Self::deposit_event(Event::CollectionPropertyDeleted(1119 collection.id,1120 property_key,1121 ));11221123 Ok(())1124 }11251126 1127 1128 1129 1130 1131 #[transactional]1132 pub fn delete_collection_properties(1133 collection: &CollectionHandle<T>,1134 sender: &T::CrossAccountId,1135 property_keys: Vec<PropertyKey>,1136 ) -> DispatchResult {1137 for key in property_keys {1138 Self::delete_collection_property(collection, sender, key)?;1139 }11401141 Ok(())1142 }11431144 1145 1146 1147 1148 1149 1150 pub fn set_property_permission_unchecked(1151 collection: CollectionId,1152 property_permission: PropertyKeyPermission,1153 ) -> DispatchResult {1154 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1155 permissions.try_set(property_permission.key, property_permission.permission)1156 })1157 .map_err(<Error<T>>::from)?;1158 Ok(())1159 }11601161 1162 1163 1164 1165 1166 pub fn set_property_permission(1167 collection: &CollectionHandle<T>,1168 sender: &T::CrossAccountId,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_set(property_permission.key, property_permission.permission)1185 })1186 .map_err(<Error<T>>::from)?;11871188 Self::deposit_event(Event::PropertyPermissionSet(1189 collection.id,1190 property_permission.key,1191 ));11921193 Ok(())1194 }11951196 1197 1198 1199 1200 1201 #[transactional]1202 pub fn set_token_property_permissions(1203 collection: &CollectionHandle<T>,1204 sender: &T::CrossAccountId,1205 property_permissions: Vec<PropertyKeyPermission>,1206 ) -> DispatchResult {1207 for prop_pemission in property_permissions {1208 Self::set_property_permission(collection, sender, prop_pemission)?;1209 }12101211 Ok(())1212 }12131214 1215 pub fn get_collection_property(1216 collection_id: CollectionId,1217 key: &PropertyKey,1218 ) -> Option<PropertyValue> {1219 Self::collection_properties(collection_id).get(key).cloned()1220 }12211222 1223 pub fn bytes_keys_to_property_keys(1224 keys: Vec<Vec<u8>>,1225 ) -> Result<Vec<PropertyKey>, DispatchError> {1226 keys.into_iter()1227 .map(|key| -> Result<PropertyKey, DispatchError> {1228 key.try_into()1229 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1230 })1231 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1232 }12331234 1235 pub fn filter_collection_properties(1236 collection_id: CollectionId,1237 keys: Option<Vec<PropertyKey>>,1238 ) -> Result<Vec<Property>, DispatchError> {1239 let properties = Self::collection_properties(collection_id);12401241 let properties = keys1242 .map(|keys| {1243 keys.into_iter()1244 .filter_map(|key| {1245 properties.get(&key).map(|value| Property {1246 key,1247 value: value.clone(),1248 })1249 })1250 .collect()1251 })1252 .unwrap_or_else(|| {1253 properties1254 .into_iter()1255 .map(|(key, value)| Property { key, value })1256 .collect()1257 });12581259 Ok(properties)1260 }12611262 1263 pub fn filter_property_permissions(1264 collection_id: CollectionId,1265 keys: Option<Vec<PropertyKey>>,1266 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1267 let permissions = Self::property_permissions(collection_id);12681269 let key_permissions = keys1270 .map(|keys| {1271 keys.into_iter()1272 .filter_map(|key| {1273 permissions1274 .get(&key)1275 .map(|permission| PropertyKeyPermission {1276 key,1277 permission: permission.clone(),1278 })1279 })1280 .collect()1281 })1282 .unwrap_or_else(|| {1283 permissions1284 .into_iter()1285 .map(|(key, permission)| PropertyKeyPermission { key, permission })1286 .collect()1287 });12881289 Ok(key_permissions)1290 }12911292 1293 pub fn toggle_allowlist(1294 collection: &CollectionHandle<T>,1295 sender: &T::CrossAccountId,1296 user: &T::CrossAccountId,1297 allowed: bool,1298 ) -> DispatchResult {1299 collection.check_is_owner_or_admin(sender)?;13001301 13021303 if allowed {1304 <Allowlist<T>>::insert((collection.id, user), true);1305 } else {1306 <Allowlist<T>>::remove((collection.id, user));1307 }13081309 Ok(())1310 }13111312 1313 pub fn toggle_admin(1314 collection: &CollectionHandle<T>,1315 sender: &T::CrossAccountId,1316 user: &T::CrossAccountId,1317 admin: bool,1318 ) -> DispatchResult {1319 collection.check_is_owner(sender)?;13201321 let was_admin = <IsAdmin<T>>::get((collection.id, user));1322 if was_admin == admin {1323 return Ok(());1324 }1325 let amount = <AdminAmount<T>>::get(collection.id);13261327 if admin {1328 let amount = amount1329 .checked_add(1)1330 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1331 ensure!(1332 amount <= Self::collection_admins_limit(),1333 <Error<T>>::CollectionAdminCountExceeded,1334 );13351336 13371338 <AdminAmount<T>>::insert(collection.id, amount);1339 <IsAdmin<T>>::insert((collection.id, user), true);1340 } else {1341 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1342 <IsAdmin<T>>::remove((collection.id, user));1343 }13441345 Ok(())1346 }13471348 1349 pub fn clamp_limits(1350 mode: CollectionMode,1351 old_limit: &CollectionLimits,1352 mut new_limit: CollectionLimits,1353 ) -> Result<CollectionLimits, DispatchError> {1354 let limits = old_limit;1355 limit_default!(old_limit, new_limit,1356 account_token_ownership_limit => ensure!(1357 new_limit <= MAX_TOKEN_OWNERSHIP,1358 <Error<T>>::CollectionLimitBoundsExceeded,1359 ),1360 sponsored_data_size => ensure!(1361 new_limit <= CUSTOM_DATA_LIMIT,1362 <Error<T>>::CollectionLimitBoundsExceeded,1363 ),13641365 sponsored_data_rate_limit => {},1366 token_limit => ensure!(1367 old_limit >= new_limit && new_limit > 0,1368 <Error<T>>::CollectionTokenLimitExceeded1369 ),13701371 sponsor_transfer_timeout(match mode {1372 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1373 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1374 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1375 }) => ensure!(1376 new_limit <= MAX_SPONSOR_TIMEOUT,1377 <Error<T>>::CollectionLimitBoundsExceeded,1378 ),1379 sponsor_approve_timeout => {},1380 owner_can_transfer => ensure!(1381 !limits.owner_can_transfer_instaled() ||1382 old_limit || !new_limit,1383 <Error<T>>::OwnerPermissionsCantBeReverted,1384 ),1385 owner_can_destroy => ensure!(1386 old_limit || !new_limit,1387 <Error<T>>::OwnerPermissionsCantBeReverted,1388 ),1389 transfers_enabled => {},1390 );1391 Ok(new_limit)1392 }13931394 1395 pub fn clamp_permissions(1396 _mode: CollectionMode,1397 old_permission: &CollectionPermissions,1398 mut new_permission: CollectionPermissions,1399 ) -> Result<CollectionPermissions, DispatchError> {1400 limit_default_clone!(old_permission, new_permission,1401 access => {},1402 mint_mode => {},1403 nesting => { },1404 );1405 Ok(new_permission)1406 }1407}140814091410#[macro_export]1411macro_rules! unsupported {1412 () => {1413 Err(<Error<T>>::UnsupportedOperation.into())1414 };1415}141614171418pub trait CommonWeightInfo<CrossAccountId> {1419 1420 fn create_item() -> Weight;14211422 1423 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14241425 1426 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14271428 1429 fn burn_item() -> Weight;14301431 1432 1433 1434 fn set_collection_properties(amount: u32) -> Weight;14351436 1437 1438 1439 fn delete_collection_properties(amount: u32) -> Weight;14401441 1442 1443 1444 fn set_token_properties(amount: u32) -> Weight;14451446 1447 1448 1449 fn delete_token_properties(amount: u32) -> Weight;14501451 1452 1453 1454 fn set_token_property_permissions(amount: u32) -> Weight;14551456 1457 fn transfer() -> Weight;14581459 1460 fn approve() -> Weight;14611462 1463 fn transfer_from() -> Weight;14641465 1466 fn burn_from() -> Weight;14671468 1469 1470 1471 1472 fn burn_recursively_self_raw() -> Weight;14731474 1475 1476 1477 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14781479 1480 1481 1482 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1483 Self::burn_recursively_self_raw()1484 .saturating_mul(max_selfs.max(1) as u64)1485 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1486 }1487}148814891490pub trait RefungibleExtensionsWeightInfo {1491 1492 fn repartition() -> Weight;1493}149414951496149714981499pub trait CommonCollectionOperations<T: Config> {1500 1501 1502 1503 1504 1505 1506 fn create_item(1507 &self,1508 sender: T::CrossAccountId,1509 to: T::CrossAccountId,1510 data: CreateItemData,1511 nesting_budget: &dyn Budget,1512 ) -> DispatchResultWithPostInfo;15131514 1515 1516 1517 1518 1519 1520 fn create_multiple_items(1521 &self,1522 sender: T::CrossAccountId,1523 to: T::CrossAccountId,1524 data: Vec<CreateItemData>,1525 nesting_budget: &dyn Budget,1526 ) -> DispatchResultWithPostInfo;15271528 1529 1530 1531 1532 1533 1534 fn create_multiple_items_ex(1535 &self,1536 sender: T::CrossAccountId,1537 data: CreateItemExData<T::CrossAccountId>,1538 nesting_budget: &dyn Budget,1539 ) -> DispatchResultWithPostInfo;15401541 1542 1543 1544 1545 1546 fn burn_item(1547 &self,1548 sender: T::CrossAccountId,1549 token: TokenId,1550 amount: u128,1551 ) -> DispatchResultWithPostInfo;15521553 1554 1555 1556 1557 1558 1559 fn burn_item_recursively(1560 &self,1561 sender: T::CrossAccountId,1562 token: TokenId,1563 self_budget: &dyn Budget,1564 breadth_budget: &dyn Budget,1565 ) -> DispatchResultWithPostInfo;15661567 1568 1569 1570 1571 fn set_collection_properties(1572 &self,1573 sender: T::CrossAccountId,1574 properties: Vec<Property>,1575 ) -> DispatchResultWithPostInfo;15761577 1578 1579 1580 1581 fn delete_collection_properties(1582 &self,1583 sender: &T::CrossAccountId,1584 property_keys: Vec<PropertyKey>,1585 ) -> DispatchResultWithPostInfo;15861587 1588 1589 1590 1591 1592 1593 1594 1595 1596 fn set_token_properties(1597 &self,1598 sender: T::CrossAccountId,1599 token_id: TokenId,1600 properties: Vec<Property>,1601 budget: &dyn Budget,1602 ) -> DispatchResultWithPostInfo;16031604 1605 1606 1607 1608 1609 1610 1611 1612 1613 fn delete_token_properties(1614 &self,1615 sender: T::CrossAccountId,1616 token_id: TokenId,1617 property_keys: Vec<PropertyKey>,1618 budget: &dyn Budget,1619 ) -> DispatchResultWithPostInfo;16201621 1622 1623 1624 1625 1626 1627 fn set_token_property_permissions(1628 &self,1629 sender: &T::CrossAccountId,1630 property_permissions: Vec<PropertyKeyPermission>,1631 ) -> DispatchResultWithPostInfo;16321633 1634 1635 1636 1637 1638 1639 1640 fn transfer(1641 &self,1642 sender: T::CrossAccountId,1643 to: T::CrossAccountId,1644 token: TokenId,1645 amount: u128,1646 budget: &dyn Budget,1647 ) -> DispatchResultWithPostInfo;16481649 1650 1651 1652 1653 1654 1655 fn approve(1656 &self,1657 sender: T::CrossAccountId,1658 spender: T::CrossAccountId,1659 token: TokenId,1660 amount: u128,1661 ) -> DispatchResultWithPostInfo;16621663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 fn transfer_from(1674 &self,1675 sender: T::CrossAccountId,1676 from: T::CrossAccountId,1677 to: T::CrossAccountId,1678 token: TokenId,1679 amount: u128,1680 budget: &dyn Budget,1681 ) -> DispatchResultWithPostInfo;16821683 1684 1685 1686 1687 1688 1689 1690 1691 1692 fn burn_from(1693 &self,1694 sender: T::CrossAccountId,1695 from: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 budget: &dyn Budget,1699 ) -> DispatchResultWithPostInfo;17001701 1702 1703 1704 1705 1706 1707 fn check_nesting(1708 &self,1709 sender: T::CrossAccountId,1710 from: (CollectionId, TokenId),1711 under: TokenId,1712 budget: &dyn Budget,1713 ) -> DispatchResult;17141715 1716 1717 1718 1719 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17201721 1722 1723 1724 1725 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17261727 1728 1729 1730 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17311732 1733 fn collection_tokens(&self) -> Vec<TokenId>;17341735 1736 1737 1738 fn token_exists(&self, token: TokenId) -> bool;17391740 1741 fn last_token_id(&self) -> TokenId;17421743 1744 1745 1746 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17471748 1749 1750 1751 1752 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17531754 1755 1756 1757 1758 1759 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17601761 1762 fn total_supply(&self) -> u32;17631764 1765 1766 1767 fn account_balance(&self, account: T::CrossAccountId) -> u32;17681769 1770 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17711772 1773 fn total_pieces(&self, token: TokenId) -> Option<u128>;17741775 1776 1777 1778 1779 1780 fn allowance(1781 &self,1782 sender: T::CrossAccountId,1783 spender: T::CrossAccountId,1784 token: TokenId,1785 ) -> u128;17861787 1788 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1789}179017911792pub trait RefungibleExtensions<T>1793where1794 T: Config,1795{1796 1797 1798 1799 1800 1801 1802 1803 fn repartition(1804 &self,1805 sender: &T::CrossAccountId,1806 token: TokenId,1807 amount: u128,1808 ) -> DispatchResultWithPostInfo;1809}18101811181218131814pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1815 let post_info = PostDispatchInfo {1816 actual_weight: Some(weight),1817 pays_fee: Pays::Yes,1818 };1819 match res {1820 Ok(()) => Ok(post_info),1821 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1822 }1823}18241825impl<T: Config> From<PropertiesError> for Error<T> {1826 fn from(error: PropertiesError) -> Self {1827 match error {1828 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1829 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1830 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1831 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1832 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1833 }1834 }1835}