1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#![warn(missing_docs)]54#![cfg_attr(not(feature = "std"), no_std)]55extern crate alloc;5657use core::ops::{Deref, DerefMut};58use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};59use sp_std::vec::Vec;60use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};61use evm_coder::ToLog;62use frame_support::{63 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},64 ensure,65 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},66 weights::Pays,67 transactional,68};69use pallet_evm::GasWeightMapping;70use up_data_structs::{71 COLLECTION_NUMBER_LIMIT,72 Collection,73 RpcCollection,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 SchemaVersion,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132133134135136137138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 176 177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 ))194 }195196 197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 ))204 }205206 207 pub fn save(self) -> DispatchResult {208 <CollectionById<T>>::insert(self.id, self.collection);209 Ok(())210 }211212 213 214 215 216 217 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {218 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);219 Ok(())220 }221222 223 224 225 226 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {227 if self.collection.sponsorship.pending_sponsor() != Some(sender) {228 return Ok(false);229 }230231 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());232 Ok(true)233 }234235 236 237 pub fn check_is_internal(&self) -> DispatchResult {238 if self.external_collection {239 return Err(<Error<T>>::CollectionIsExternal)?;240 }241242 Ok(())243 }244245 246 247 pub fn check_is_external(&self) -> DispatchResult {248 if !self.external_collection {249 return Err(<Error<T>>::CollectionIsInternal)?;250 }251252 Ok(())253 }254}255256impl<T: Config> Deref for CollectionHandle<T> {257 type Target = Collection<T::AccountId>;258259 fn deref(&self) -> &Self::Target {260 &self.collection261 }262}263264impl<T: Config> DerefMut for CollectionHandle<T> {265 fn deref_mut(&mut self) -> &mut Self::Target {266 &mut self.collection267 }268}269270impl<T: Config> CollectionHandle<T> {271 272 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {273 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);274 Ok(())275 }276277 278 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {279 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))280 }281282 283 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {284 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);285 Ok(())286 }287288 289 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {290 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)291 }292293 294 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {295 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)296 }297298 299 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {300 ensure!(301 <Allowlist<T>>::get((self.id, user)),302 <Error<T>>::AddressNotInAllowlist303 );304 Ok(())305 }306}307308#[frame_support::pallet]309pub mod pallet {310 use super::*;311 use pallet_evm::account;312 use dispatch::CollectionDispatch;313 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};314 use frame_system::pallet_prelude::*;315 use frame_support::traits::Currency;316 use up_data_structs::{TokenId, mapping::TokenAddressMapping};317 use scale_info::TypeInfo;318 use weights::WeightInfo;319320 #[pallet::config]321 pub trait Config:322 frame_system::Config323 + pallet_evm_coder_substrate::Config324 + pallet_evm::Config325 + TypeInfo326 + account::Config327 {328 329 type WeightInfo: WeightInfo;330331 332 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;333334 335 type Currency: Currency<Self::AccountId>;336337 338 #[pallet::constant]339 type CollectionCreationPrice: Get<340 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,341 >;342343 344 type CollectionDispatch: CollectionDispatch<Self>;345346 347 type TreasuryAccountId: Get<Self::AccountId>;348349 350 type ContractAddress: Get<H160>;351352 353 type EvmTokenAddressMapping: TokenAddressMapping<H160>;354355 356 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;357 }358359 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);360361 #[pallet::pallet]362 #[pallet::storage_version(STORAGE_VERSION)]363 #[pallet::generate_store(pub(super) trait Store)]364 pub struct Pallet<T>(_);365366 #[pallet::extra_constants]367 impl<T: Config> Pallet<T> {368 369 pub fn collection_admins_limit() -> u32 {370 COLLECTION_ADMINS_LIMIT371 }372 }373374 #[pallet::event]375 #[pallet::generate_deposit(pub fn deposit_event)]376 pub enum Event<T: Config> {377 378 CollectionCreated(379 380 CollectionId,381 382 u8,383 384 T::AccountId,385 ),386387 388 CollectionDestroyed(389 390 CollectionId,391 ),392393 394 ItemCreated(395 396 CollectionId,397 398 TokenId,399 400 T::CrossAccountId,401 402 u128,403 ),404405 406 ItemDestroyed(407 408 CollectionId,409 410 TokenId,411 412 T::CrossAccountId,413 414 u128,415 ),416417 418 Transfer(419 420 CollectionId,421 422 TokenId,423 424 T::CrossAccountId,425 426 T::CrossAccountId,427 428 u128,429 ),430431 432 Approved(433 434 CollectionId,435 436 TokenId,437 438 T::CrossAccountId,439 440 T::CrossAccountId,441 442 u128,443 ),444445 446 CollectionPropertySet(447 448 CollectionId,449 450 PropertyKey,451 ),452453 454 CollectionPropertyDeleted(455 456 CollectionId,457 458 PropertyKey,459 ),460461 462 TokenPropertySet(463 464 CollectionId,465 466 TokenId,467 468 PropertyKey,469 ),470471 472 TokenPropertyDeleted(473 474 CollectionId,475 476 TokenId,477 478 PropertyKey,479 ),480481 482 PropertyPermissionSet(483 484 CollectionId,485 486 PropertyKey,487 ),488 }489490 #[pallet::error]491 pub enum Error<T> {492 493 CollectionNotFound,494 495 MustBeTokenOwner,496 497 NoPermission,498 499 CantDestroyNotEmptyCollection,500 501 PublicMintingNotAllowed,502 503 AddressNotInAllowlist,504505 506 CollectionNameLimitExceeded,507 508 CollectionDescriptionLimitExceeded,509 510 CollectionTokenPrefixLimitExceeded,511 512 TotalCollectionsLimitExceeded,513 514 CollectionAdminCountExceeded,515 516 CollectionLimitBoundsExceeded,517 518 OwnerPermissionsCantBeReverted,519 520 TransferNotAllowed,521 522 AccountTokenLimitExceeded,523 524 CollectionTokenLimitExceeded,525 526 MetadataFlagFrozen,527528 529 TokenNotFound,530 531 TokenValueTooLow,532 533 ApprovedValueTooLow,534 535 CantApproveMoreThanOwned,536537 538 AddressIsZero,539 540 UnsupportedOperation,541542 543 NotSufficientFounds,544545 546 UserIsNotAllowedToNest,547 548 SourceCollectionIsNotAllowedToNest,549550 551 CollectionFieldSizeExceeded,552553 554 NoSpaceForProperty,555556 557 PropertyLimitReached,558559 560 PropertyKeyIsTooLong,561562 563 InvalidCharacterInPropertyKey,564565 566 EmptyPropertyKey,567568 569 CollectionIsExternal,570571 572 CollectionIsInternal,573 }574575 576 #[pallet::storage]577 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;578579 580 #[pallet::storage]581 pub type DestroyedCollectionCount<T> =582 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;583584 585 #[pallet::storage]586 pub type CollectionById<T> = StorageMap<587 Hasher = Blake2_128Concat,588 Key = CollectionId,589 Value = Collection<<T as frame_system::Config>::AccountId>,590 QueryKind = OptionQuery,591 >;592593 594 #[pallet::storage]595 #[pallet::getter(fn collection_properties)]596 pub type CollectionProperties<T> = StorageMap<597 Hasher = Blake2_128Concat,598 Key = CollectionId,599 Value = Properties,600 QueryKind = ValueQuery,601 OnEmpty = up_data_structs::CollectionProperties,602 >;603604 605 #[pallet::storage]606 #[pallet::getter(fn property_permissions)]607 pub type CollectionPropertyPermissions<T> = StorageMap<608 Hasher = Blake2_128Concat,609 Key = CollectionId,610 Value = PropertiesPermissionMap,611 QueryKind = ValueQuery,612 >;613614 615 #[pallet::storage]616 pub type AdminAmount<T> = StorageMap<617 Hasher = Blake2_128Concat,618 Key = CollectionId,619 Value = u32,620 QueryKind = ValueQuery,621 >;622623 624 #[pallet::storage]625 pub type IsAdmin<T: Config> = StorageNMap<626 Key = (627 Key<Blake2_128Concat, CollectionId>,628 Key<Blake2_128Concat, T::CrossAccountId>,629 ),630 Value = bool,631 QueryKind = ValueQuery,632 >;633634 635 #[pallet::storage]636 pub type Allowlist<T: Config> = StorageNMap<637 Key = (638 Key<Blake2_128Concat, CollectionId>,639 Key<Blake2_128Concat, T::CrossAccountId>,640 ),641 Value = bool,642 QueryKind = ValueQuery,643 >;644645 646 #[pallet::storage]647 pub type DummyStorageValue<T: Config> = StorageValue<648 Value = (649 CollectionStats,650 CollectionId,651 TokenId,652 TokenChild,653 PhantomType<(654 TokenData<T::CrossAccountId>,655 RpcCollection<T::AccountId>,656 657 RmrkCollectionInfo<T::AccountId>,658 RmrkInstanceInfo<T::AccountId>,659 RmrkResourceInfo,660 RmrkPropertyInfo,661 RmrkBaseInfo<T::AccountId>,662 RmrkPartType,663 RmrkBoundedTheme,664 RmrkNftChild,665 )>,666 ),667 QueryKind = OptionQuery,668 >;669670 #[pallet::hooks]671 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {672 fn on_runtime_upgrade() -> Weight {673 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {674 use up_data_structs::{CollectionVersion1, CollectionVersion2};675 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {676 let mut props = Vec::new();677 if !v.offchain_schema.is_empty() {678 props.push(Property {679 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),680 value: v681 .offchain_schema682 .clone()683 .into_inner()684 .try_into()685 .expect("offchain schema too big"),686 });687 }688 if !v.variable_on_chain_schema.is_empty() {689 props.push(Property {690 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),691 value: v692 .variable_on_chain_schema693 .clone()694 .into_inner()695 .try_into()696 .expect("offchain schema too big"),697 });698 }699 if !v.const_on_chain_schema.is_empty() {700 props.push(Property {701 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),702 value: v703 .const_on_chain_schema704 .clone()705 .into_inner()706 .try_into()707 .expect("offchain schema too big"),708 });709 }710 props.push(Property {711 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),712 value: match v.schema_version {713 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),714 SchemaVersion::Unique => b"Unique".as_slice(),715 }716 .to_vec()717 .try_into()718 .unwrap(),719 });720 Self::set_scoped_collection_properties(721 id,722 PropertyScope::None,723 props.into_iter(),724 )725 .expect("existing data larger than properties");726 let mut new = CollectionVersion2::from(v.clone());727 new.permissions.access = Some(v.access);728 new.permissions.mint_mode = Some(v.mint_mode);729 Some(new)730 });731 }732733 0734 }735 }736}737738impl<T: Config> Pallet<T> {739 740 741 742 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {743 ensure!(744 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,745 <Error<T>>::AddressIsZero746 );747 Ok(())748 }749750 751 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {752 <IsAdmin<T>>::iter_prefix((collection,))753 .map(|(a, _)| a)754 .collect()755 }756757 758 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {759 <Allowlist<T>>::iter_prefix((collection,))760 .map(|(a, _)| a)761 .collect()762 }763764 765 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {766 <Allowlist<T>>::get((collection, user))767 }768769 770 pub fn collection_stats() -> CollectionStats {771 let created = <CreatedCollectionCount<T>>::get();772 let destroyed = <DestroyedCollectionCount<T>>::get();773 CollectionStats {774 created: created.0,775 destroyed: destroyed.0,776 alive: created.0 - destroyed.0,777 }778 }779780 781 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {782 let collection = <CollectionById<T>>::get(collection);783 if collection.is_none() {784 return None;785 }786787 let collection = collection.unwrap();788 let limits = collection.limits;789 let effective_limits = CollectionLimits {790 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),791 sponsored_data_size: Some(limits.sponsored_data_size()),792 sponsored_data_rate_limit: Some(793 limits794 .sponsored_data_rate_limit795 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),796 ),797 token_limit: Some(limits.token_limit()),798 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(799 match collection.mode {800 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,801 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,802 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,803 },804 )),805 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),806 owner_can_transfer: Some(limits.owner_can_transfer()),807 owner_can_destroy: Some(limits.owner_can_destroy()),808 transfers_enabled: Some(limits.transfers_enabled()),809 };810811 Some(effective_limits)812 }813814 815 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {816 let Collection {817 name,818 description,819 owner,820 mode,821 token_prefix,822 sponsorship,823 limits,824 permissions,825 external_collection,826 } = <CollectionById<T>>::get(collection)?;827828 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)829 .into_iter()830 .map(|(key, permission)| PropertyKeyPermission { key, permission })831 .collect();832833 let properties = <CollectionProperties<T>>::get(collection)834 .into_iter()835 .map(|(key, value)| Property { key, value })836 .collect();837838 let permissions = CollectionPermissions {839 access: Some(permissions.access()),840 mint_mode: Some(permissions.mint_mode()),841 nesting: Some(permissions.nesting().clone()),842 };843844 Some(RpcCollection {845 name: name.into_inner(),846 description: description.into_inner(),847 owner,848 mode,849 token_prefix: token_prefix.into_inner(),850 sponsorship,851 limits,852 permissions,853 token_property_permissions,854 properties,855 read_only: external_collection,856 })857 }858}859860macro_rules! limit_default {861 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{862 $(863 if let Some($new) = $new.$field {864 let $old = $old.$field($($arg)?);865 let _ = $new;866 let _ = $old;867 $check868 } else {869 $new.$field = $old.$field870 }871 )*872 }};873}874macro_rules! limit_default_clone {875 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{876 $(877 if let Some($new) = $new.$field.clone() {878 let $old = $old.$field($($arg)?);879 let _ = $new;880 let _ = $old;881 $check882 } else {883 $new.$field = $old.$field.clone()884 }885 )*886 }};887}888889impl<T: Config> Pallet<T> {890 891 892 893 894 895 pub fn init_collection(896 owner: T::CrossAccountId,897 data: CreateCollectionData<T::AccountId>,898 is_external: bool,899 ) -> Result<CollectionId, DispatchError> {900 {901 ensure!(902 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,903 Error::<T>::CollectionTokenPrefixLimitExceeded904 );905 }906907 let created_count = <CreatedCollectionCount<T>>::get()908 .0909 .checked_add(1)910 .ok_or(ArithmeticError::Overflow)?;911 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;912 let id = CollectionId(created_count);913914 915 ensure!(916 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,917 <Error<T>>::TotalCollectionsLimitExceeded918 );919920 921922 let collection = Collection {923 owner: owner.as_sub().clone(),924 name: data.name,925 mode: data.mode.clone(),926 description: data.description,927 token_prefix: data.token_prefix,928 sponsorship: data929 .pending_sponsor930 .map(SponsorshipState::Unconfirmed)931 .unwrap_or_default(),932 limits: data933 .limits934 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))935 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,936 permissions: data937 .permissions938 .map(|permissions| {939 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)940 })941 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,942 external_collection: is_external,943 };944945 let mut collection_properties = up_data_structs::CollectionProperties::get();946 collection_properties947 .try_set_from_iter(data.properties.into_iter())948 .map_err(<Error<T>>::from)?;949950 CollectionProperties::<T>::insert(id, collection_properties);951952 let mut token_props_permissions = PropertiesPermissionMap::new();953 token_props_permissions954 .try_set_from_iter(data.token_property_permissions.into_iter())955 .map_err(<Error<T>>::from)?;956957 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);958959 960 {961 let mut imbalance =962 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();963 imbalance.subsume(964 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(965 &T::TreasuryAccountId::get(),966 T::CollectionCreationPrice::get(),967 ),968 );969 <T as Config>::Currency::settle(970 &owner.as_sub(),971 imbalance,972 WithdrawReasons::TRANSFER,973 ExistenceRequirement::KeepAlive,974 )975 .map_err(|_| Error::<T>::NotSufficientFounds)?;976 }977978 <CreatedCollectionCount<T>>::put(created_count);979 <Pallet<T>>::deposit_event(Event::CollectionCreated(980 id,981 data.mode.id(),982 owner.as_sub().clone(),983 ));984 <PalletEvm<T>>::deposit_log(985 erc::CollectionHelpersEvents::CollectionCreated {986 owner: *owner.as_eth(),987 collection_id: eth::collection_id_to_address(id),988 }989 .to_log(T::ContractAddress::get()),990 );991 <CollectionById<T>>::insert(id, collection);992 Ok(id)993 }994995 996 997 998 999 pub fn destroy_collection(1000 collection: CollectionHandle<T>,1001 sender: &T::CrossAccountId,1002 ) -> DispatchResult {1003 ensure!(1004 collection.limits.owner_can_destroy(),1005 <Error<T>>::NoPermission,1006 );1007 collection.check_is_owner(sender)?;10081009 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1010 .01011 .checked_add(1)1012 .ok_or(ArithmeticError::Overflow)?;10131014 10151016 <DestroyedCollectionCount<T>>::put(destroyed_collections);1017 <CollectionById<T>>::remove(collection.id);1018 <AdminAmount<T>>::remove(collection.id);1019 <IsAdmin<T>>::remove_prefix((collection.id,), None);1020 <Allowlist<T>>::remove_prefix((collection.id,), None);1021 <CollectionProperties<T>>::remove(collection.id);10221023 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));1024 Ok(())1025 }10261027 1028 1029 1030 1031 1032 pub fn set_collection_property(1033 collection: &CollectionHandle<T>,1034 sender: &T::CrossAccountId,1035 property: Property,1036 ) -> DispatchResult {1037 collection.check_is_owner_or_admin(sender)?;10381039 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1040 let property = property.clone();1041 properties.try_set(property.key, property.value)1042 })1043 .map_err(<Error<T>>::from)?;10441045 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10461047 Ok(())1048 }10491050 1051 1052 1053 1054 1055 pub fn set_scoped_collection_property(1056 collection_id: CollectionId,1057 scope: PropertyScope,1058 property: Property,1059 ) -> DispatchResult {1060 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1061 properties.try_scoped_set(scope, property.key, property.value)1062 })1063 .map_err(<Error<T>>::from)?;10641065 Ok(())1066 }10671068 1069 1070 1071 1072 1073 pub fn set_scoped_collection_properties(1074 collection_id: CollectionId,1075 scope: PropertyScope,1076 properties: impl Iterator<Item = Property>,1077 ) -> DispatchResult {1078 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1079 stored_properties.try_scoped_set_from_iter(scope, properties)1080 })1081 .map_err(<Error<T>>::from)?;10821083 Ok(())1084 }10851086 1087 1088 1089 1090 1091 #[transactional]1092 pub fn set_collection_properties(1093 collection: &CollectionHandle<T>,1094 sender: &T::CrossAccountId,1095 properties: Vec<Property>,1096 ) -> DispatchResult {1097 for property in properties {1098 Self::set_collection_property(collection, sender, property)?;1099 }11001101 Ok(())1102 }11031104 1105 1106 1107 1108 1109 pub fn delete_collection_property(1110 collection: &CollectionHandle<T>,1111 sender: &T::CrossAccountId,1112 property_key: PropertyKey,1113 ) -> DispatchResult {1114 collection.check_is_owner_or_admin(sender)?;11151116 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1117 properties.remove(&property_key)1118 })1119 .map_err(<Error<T>>::from)?;11201121 Self::deposit_event(Event::CollectionPropertyDeleted(1122 collection.id,1123 property_key,1124 ));11251126 Ok(())1127 }11281129 1130 1131 1132 1133 1134 #[transactional]1135 pub fn delete_collection_properties(1136 collection: &CollectionHandle<T>,1137 sender: &T::CrossAccountId,1138 property_keys: Vec<PropertyKey>,1139 ) -> DispatchResult {1140 for key in property_keys {1141 Self::delete_collection_property(collection, sender, key)?;1142 }11431144 Ok(())1145 }11461147 1148 1149 1150 1151 1152 1153 pub fn set_property_permission_unchecked(1154 collection: CollectionId,1155 property_permission: PropertyKeyPermission,1156 ) -> DispatchResult {1157 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1158 permissions.try_set(property_permission.key, property_permission.permission)1159 })1160 .map_err(<Error<T>>::from)?;1161 Ok(())1162 }11631164 1165 1166 1167 1168 1169 pub fn set_property_permission(1170 collection: &CollectionHandle<T>,1171 sender: &T::CrossAccountId,1172 property_permission: PropertyKeyPermission,1173 ) -> DispatchResult {1174 collection.check_is_owner_or_admin(sender)?;11751176 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1177 let current_permission = all_permissions.get(&property_permission.key);1178 if matches![1179 current_permission,1180 Some(PropertyPermission { mutable: false, .. })1181 ] {1182 return Err(<Error<T>>::NoPermission.into());1183 }11841185 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1186 let property_permission = property_permission.clone();1187 permissions.try_set(property_permission.key, property_permission.permission)1188 })1189 .map_err(<Error<T>>::from)?;11901191 Self::deposit_event(Event::PropertyPermissionSet(1192 collection.id,1193 property_permission.key,1194 ));11951196 Ok(())1197 }11981199 1200 1201 1202 1203 1204 #[transactional]1205 pub fn set_token_property_permissions(1206 collection: &CollectionHandle<T>,1207 sender: &T::CrossAccountId,1208 property_permissions: Vec<PropertyKeyPermission>,1209 ) -> DispatchResult {1210 for prop_pemission in property_permissions {1211 Self::set_property_permission(collection, sender, prop_pemission)?;1212 }12131214 Ok(())1215 }12161217 1218 pub fn get_collection_property(1219 collection_id: CollectionId,1220 key: &PropertyKey,1221 ) -> Option<PropertyValue> {1222 Self::collection_properties(collection_id).get(key).cloned()1223 }12241225 1226 pub fn bytes_keys_to_property_keys(1227 keys: Vec<Vec<u8>>,1228 ) -> Result<Vec<PropertyKey>, DispatchError> {1229 keys.into_iter()1230 .map(|key| -> Result<PropertyKey, DispatchError> {1231 key.try_into()1232 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1233 })1234 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1235 }12361237 1238 pub fn filter_collection_properties(1239 collection_id: CollectionId,1240 keys: Option<Vec<PropertyKey>>,1241 ) -> Result<Vec<Property>, DispatchError> {1242 let properties = Self::collection_properties(collection_id);12431244 let properties = keys1245 .map(|keys| {1246 keys.into_iter()1247 .filter_map(|key| {1248 properties.get(&key).map(|value| Property {1249 key,1250 value: value.clone(),1251 })1252 })1253 .collect()1254 })1255 .unwrap_or_else(|| {1256 properties1257 .into_iter()1258 .map(|(key, value)| Property { key, value })1259 .collect()1260 });12611262 Ok(properties)1263 }12641265 1266 pub fn filter_property_permissions(1267 collection_id: CollectionId,1268 keys: Option<Vec<PropertyKey>>,1269 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1270 let permissions = Self::property_permissions(collection_id);12711272 let key_permissions = keys1273 .map(|keys| {1274 keys.into_iter()1275 .filter_map(|key| {1276 permissions1277 .get(&key)1278 .map(|permission| PropertyKeyPermission {1279 key,1280 permission: permission.clone(),1281 })1282 })1283 .collect()1284 })1285 .unwrap_or_else(|| {1286 permissions1287 .into_iter()1288 .map(|(key, permission)| PropertyKeyPermission { key, permission })1289 .collect()1290 });12911292 Ok(key_permissions)1293 }12941295 1296 pub fn toggle_allowlist(1297 collection: &CollectionHandle<T>,1298 sender: &T::CrossAccountId,1299 user: &T::CrossAccountId,1300 allowed: bool,1301 ) -> DispatchResult {1302 collection.check_is_owner_or_admin(sender)?;13031304 13051306 if allowed {1307 <Allowlist<T>>::insert((collection.id, user), true);1308 } else {1309 <Allowlist<T>>::remove((collection.id, user));1310 }13111312 Ok(())1313 }13141315 1316 pub fn toggle_admin(1317 collection: &CollectionHandle<T>,1318 sender: &T::CrossAccountId,1319 user: &T::CrossAccountId,1320 admin: bool,1321 ) -> DispatchResult {1322 collection.check_is_owner(sender)?;13231324 let was_admin = <IsAdmin<T>>::get((collection.id, user));1325 if was_admin == admin {1326 return Ok(());1327 }1328 let amount = <AdminAmount<T>>::get(collection.id);13291330 if admin {1331 let amount = amount1332 .checked_add(1)1333 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1334 ensure!(1335 amount <= Self::collection_admins_limit(),1336 <Error<T>>::CollectionAdminCountExceeded,1337 );13381339 13401341 <AdminAmount<T>>::insert(collection.id, amount);1342 <IsAdmin<T>>::insert((collection.id, user), true);1343 } else {1344 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1345 <IsAdmin<T>>::remove((collection.id, user));1346 }13471348 Ok(())1349 }13501351 1352 pub fn clamp_limits(1353 mode: CollectionMode,1354 old_limit: &CollectionLimits,1355 mut new_limit: CollectionLimits,1356 ) -> Result<CollectionLimits, DispatchError> {1357 let limits = old_limit;1358 limit_default!(old_limit, new_limit,1359 account_token_ownership_limit => ensure!(1360 new_limit <= MAX_TOKEN_OWNERSHIP,1361 <Error<T>>::CollectionLimitBoundsExceeded,1362 ),1363 sponsored_data_size => ensure!(1364 new_limit <= CUSTOM_DATA_LIMIT,1365 <Error<T>>::CollectionLimitBoundsExceeded,1366 ),13671368 sponsored_data_rate_limit => {},1369 token_limit => ensure!(1370 old_limit >= new_limit && new_limit > 0,1371 <Error<T>>::CollectionTokenLimitExceeded1372 ),13731374 sponsor_transfer_timeout(match mode {1375 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1376 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1377 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1378 }) => ensure!(1379 new_limit <= MAX_SPONSOR_TIMEOUT,1380 <Error<T>>::CollectionLimitBoundsExceeded,1381 ),1382 sponsor_approve_timeout => {},1383 owner_can_transfer => ensure!(1384 !limits.owner_can_transfer_instaled() ||1385 old_limit || !new_limit,1386 <Error<T>>::OwnerPermissionsCantBeReverted,1387 ),1388 owner_can_destroy => ensure!(1389 old_limit || !new_limit,1390 <Error<T>>::OwnerPermissionsCantBeReverted,1391 ),1392 transfers_enabled => {},1393 );1394 Ok(new_limit)1395 }13961397 1398 pub fn clamp_permissions(1399 _mode: CollectionMode,1400 old_permission: &CollectionPermissions,1401 mut new_permission: CollectionPermissions,1402 ) -> Result<CollectionPermissions, DispatchError> {1403 limit_default_clone!(old_permission, new_permission,1404 access => {},1405 mint_mode => {},1406 nesting => { },1407 );1408 Ok(new_permission)1409 }1410}141114121413#[macro_export]1414macro_rules! unsupported {1415 () => {1416 Err(<Error<T>>::UnsupportedOperation.into())1417 };1418}141914201421pub trait CommonWeightInfo<CrossAccountId> {1422 1423 fn create_item() -> Weight;14241425 1426 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14271428 1429 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14301431 1432 fn burn_item() -> Weight;14331434 1435 1436 1437 fn set_collection_properties(amount: u32) -> Weight;14381439 1440 1441 1442 fn delete_collection_properties(amount: u32) -> Weight;14431444 1445 1446 1447 fn set_token_properties(amount: u32) -> Weight;14481449 1450 1451 1452 fn delete_token_properties(amount: u32) -> Weight;14531454 1455 1456 1457 fn set_token_property_permissions(amount: u32) -> Weight;14581459 1460 fn transfer() -> Weight;14611462 1463 fn approve() -> Weight;14641465 1466 fn transfer_from() -> Weight;14671468 1469 fn burn_from() -> Weight;14701471 1472 1473 1474 1475 fn burn_recursively_self_raw() -> Weight;14761477 1478 1479 1480 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14811482 1483 1484 1485 1486 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1487 Self::burn_recursively_self_raw()1488 .saturating_mul(max_selfs.max(1) as u64)1489 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1490 }1491}149214931494pub trait RefungibleExtensionsWeightInfo {1495 1496 fn repartition() -> Weight;1497}149814991500150115021503pub trait CommonCollectionOperations<T: Config> {1504 1505 1506 1507 1508 1509 1510 fn create_item(1511 &self,1512 sender: T::CrossAccountId,1513 to: T::CrossAccountId,1514 data: CreateItemData,1515 nesting_budget: &dyn Budget,1516 ) -> DispatchResultWithPostInfo;15171518 1519 1520 1521 1522 1523 1524 fn create_multiple_items(1525 &self,1526 sender: T::CrossAccountId,1527 to: T::CrossAccountId,1528 data: Vec<CreateItemData>,1529 nesting_budget: &dyn Budget,1530 ) -> DispatchResultWithPostInfo;15311532 1533 1534 1535 1536 1537 1538 fn create_multiple_items_ex(1539 &self,1540 sender: T::CrossAccountId,1541 data: CreateItemExData<T::CrossAccountId>,1542 nesting_budget: &dyn Budget,1543 ) -> DispatchResultWithPostInfo;15441545 1546 1547 1548 1549 1550 fn burn_item(1551 &self,1552 sender: T::CrossAccountId,1553 token: TokenId,1554 amount: u128,1555 ) -> DispatchResultWithPostInfo;15561557 1558 1559 1560 1561 1562 1563 fn burn_item_recursively(1564 &self,1565 sender: T::CrossAccountId,1566 token: TokenId,1567 self_budget: &dyn Budget,1568 breadth_budget: &dyn Budget,1569 ) -> DispatchResultWithPostInfo;15701571 1572 1573 1574 1575 fn set_collection_properties(1576 &self,1577 sender: T::CrossAccountId,1578 properties: Vec<Property>,1579 ) -> DispatchResultWithPostInfo;15801581 1582 1583 1584 1585 fn delete_collection_properties(1586 &self,1587 sender: &T::CrossAccountId,1588 property_keys: Vec<PropertyKey>,1589 ) -> DispatchResultWithPostInfo;15901591 1592 1593 1594 1595 1596 1597 1598 1599 1600 fn set_token_properties(1601 &self,1602 sender: T::CrossAccountId,1603 token_id: TokenId,1604 properties: Vec<Property>,1605 budget: &dyn Budget,1606 ) -> DispatchResultWithPostInfo;16071608 1609 1610 1611 1612 1613 1614 1615 1616 1617 fn delete_token_properties(1618 &self,1619 sender: T::CrossAccountId,1620 token_id: TokenId,1621 property_keys: Vec<PropertyKey>,1622 budget: &dyn Budget,1623 ) -> DispatchResultWithPostInfo;16241625 1626 1627 1628 1629 1630 1631 fn set_token_property_permissions(1632 &self,1633 sender: &T::CrossAccountId,1634 property_permissions: Vec<PropertyKeyPermission>,1635 ) -> DispatchResultWithPostInfo;16361637 1638 1639 1640 1641 1642 1643 1644 fn transfer(1645 &self,1646 sender: T::CrossAccountId,1647 to: T::CrossAccountId,1648 token: TokenId,1649 amount: u128,1650 budget: &dyn Budget,1651 ) -> DispatchResultWithPostInfo;16521653 1654 1655 1656 1657 1658 1659 fn approve(1660 &self,1661 sender: T::CrossAccountId,1662 spender: T::CrossAccountId,1663 token: TokenId,1664 amount: u128,1665 ) -> DispatchResultWithPostInfo;16661667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 fn transfer_from(1678 &self,1679 sender: T::CrossAccountId,1680 from: T::CrossAccountId,1681 to: T::CrossAccountId,1682 token: TokenId,1683 amount: u128,1684 budget: &dyn Budget,1685 ) -> DispatchResultWithPostInfo;16861687 1688 1689 1690 1691 1692 1693 1694 1695 1696 fn burn_from(1697 &self,1698 sender: T::CrossAccountId,1699 from: T::CrossAccountId,1700 token: TokenId,1701 amount: u128,1702 budget: &dyn Budget,1703 ) -> DispatchResultWithPostInfo;17041705 1706 1707 1708 1709 1710 1711 fn check_nesting(1712 &self,1713 sender: T::CrossAccountId,1714 from: (CollectionId, TokenId),1715 under: TokenId,1716 budget: &dyn Budget,1717 ) -> DispatchResult;17181719 1720 1721 1722 1723 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17241725 1726 1727 1728 1729 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17301731 1732 1733 1734 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17351736 1737 fn collection_tokens(&self) -> Vec<TokenId>;17381739 1740 1741 1742 fn token_exists(&self, token: TokenId) -> bool;17431744 1745 fn last_token_id(&self) -> TokenId;17461747 1748 1749 1750 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17511752 1753 1754 1755 1756 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17571758 1759 1760 1761 1762 1763 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17641765 1766 fn total_supply(&self) -> u32;17671768 1769 1770 1771 fn account_balance(&self, account: T::CrossAccountId) -> u32;17721773 1774 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17751776 1777 fn total_pieces(&self, token: TokenId) -> Option<u128>;17781779 1780 1781 1782 1783 1784 fn allowance(1785 &self,1786 sender: T::CrossAccountId,1787 spender: T::CrossAccountId,1788 token: TokenId,1789 ) -> u128;17901791 1792 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1793}179417951796pub trait RefungibleExtensions<T>1797where1798 T: Config,1799{1800 1801 1802 1803 1804 1805 1806 1807 fn repartition(1808 &self,1809 sender: &T::CrossAccountId,1810 token: TokenId,1811 amount: u128,1812 ) -> DispatchResultWithPostInfo;1813}18141815181618171818pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1819 let post_info = PostDispatchInfo {1820 actual_weight: Some(weight),1821 pays_fee: Pays::Yes,1822 };1823 match res {1824 Ok(()) => Ok(post_info),1825 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1826 }1827}18281829impl<T: Config> From<PropertiesError> for Error<T> {1830 fn from(error: PropertiesError) -> Self {1831 match error {1832 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1833 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1834 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1835 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1836 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1837 }1838 }1839}