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 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,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};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(Weight::from_ref_time(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(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 228 229 230 231 232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 238 239 240 241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 257 258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 267 268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 329 330 331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 358 type WeightInfo: WeightInfo;359360 361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 364 type Currency: Currency<Self::AccountId>;365366 367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 373 type CollectionDispatch: CollectionDispatch<Self>;374375 376 type TreasuryAccountId: Get<Self::AccountId>;377378 379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 408 CollectionCreated(409 410 CollectionId,411 412 u8,413 414 T::AccountId,415 ),416417 418 CollectionDestroyed(419 420 CollectionId,421 ),422423 424 ItemCreated(425 426 CollectionId,427 428 TokenId,429 430 T::CrossAccountId,431 432 u128,433 ),434435 436 ItemDestroyed(437 438 CollectionId,439 440 TokenId,441 442 T::CrossAccountId,443 444 u128,445 ),446447 448 Transfer(449 450 CollectionId,451 452 TokenId,453 454 T::CrossAccountId,455 456 T::CrossAccountId,457 458 u128,459 ),460461 462 Approved(463 464 CollectionId,465 466 TokenId,467 468 T::CrossAccountId,469 470 T::CrossAccountId,471 472 u128,473 ),474475 476 CollectionPropertySet(477 478 CollectionId,479 480 PropertyKey,481 ),482483 484 CollectionPropertyDeleted(485 486 CollectionId,487 488 PropertyKey,489 ),490491 492 TokenPropertySet(493 494 CollectionId,495 496 TokenId,497 498 PropertyKey,499 ),500501 502 TokenPropertyDeleted(503 504 CollectionId,505 506 TokenId,507 508 PropertyKey,509 ),510511 512 PropertyPermissionSet(513 514 CollectionId,515 516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 523 CollectionNotFound,524 525 MustBeTokenOwner,526 527 NoPermission,528 529 CantDestroyNotEmptyCollection,530 531 PublicMintingNotAllowed,532 533 AddressNotInAllowlist,534535 536 CollectionNameLimitExceeded,537 538 CollectionDescriptionLimitExceeded,539 540 CollectionTokenPrefixLimitExceeded,541 542 TotalCollectionsLimitExceeded,543 544 CollectionAdminCountExceeded,545 546 CollectionLimitBoundsExceeded,547 548 OwnerPermissionsCantBeReverted,549 550 TransferNotAllowed,551 552 AccountTokenLimitExceeded,553 554 CollectionTokenLimitExceeded,555 556 MetadataFlagFrozen,557558 559 TokenNotFound,560 561 TokenValueTooLow,562 563 ApprovedValueTooLow,564 565 CantApproveMoreThanOwned,566567 568 AddressIsZero,569570 571 UnsupportedOperation,572573 574 NotSufficientFounds,575576 577 UserIsNotAllowedToNest,578 579 SourceCollectionIsNotAllowedToNest,580581 582 CollectionFieldSizeExceeded,583584 585 NoSpaceForProperty,586587 588 PropertyLimitReached,589590 591 PropertyKeyIsTooLong,592593 594 InvalidCharacterInPropertyKey,595596 597 EmptyPropertyKey,598599 600 CollectionIsExternal,601602 603 CollectionIsInternal,604605 606 ZeroTransferNotAllowed,607 }608609 610 #[pallet::storage]611 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;612613 614 #[pallet::storage]615 pub type DestroyedCollectionCount<T> =616 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;617618 619 #[pallet::storage]620 pub type CollectionById<T> = StorageMap<621 Hasher = Blake2_128Concat,622 Key = CollectionId,623 Value = Collection<<T as frame_system::Config>::AccountId>,624 QueryKind = OptionQuery,625 >;626627 628 #[pallet::storage]629 #[pallet::getter(fn collection_properties)]630 pub type CollectionProperties<T> = StorageMap<631 Hasher = Blake2_128Concat,632 Key = CollectionId,633 Value = Properties,634 QueryKind = ValueQuery,635 OnEmpty = up_data_structs::CollectionProperties,636 >;637638 639 #[pallet::storage]640 #[pallet::getter(fn property_permissions)]641 pub type CollectionPropertyPermissions<T> = StorageMap<642 Hasher = Blake2_128Concat,643 Key = CollectionId,644 Value = PropertiesPermissionMap,645 QueryKind = ValueQuery,646 >;647648 649 #[pallet::storage]650 pub type AdminAmount<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = u32,654 QueryKind = ValueQuery,655 >;656657 658 #[pallet::storage]659 pub type IsAdmin<T: Config> = StorageNMap<660 Key = (661 Key<Blake2_128Concat, CollectionId>,662 Key<Blake2_128Concat, T::CrossAccountId>,663 ),664 Value = bool,665 QueryKind = ValueQuery,666 >;667668 669 #[pallet::storage]670 pub type Allowlist<T: Config> = StorageNMap<671 Key = (672 Key<Blake2_128Concat, CollectionId>,673 Key<Blake2_128Concat, T::CrossAccountId>,674 ),675 Value = bool,676 QueryKind = ValueQuery,677 >;678679 680 #[pallet::storage]681 pub type DummyStorageValue<T: Config> = StorageValue<682 Value = (683 CollectionStats,684 CollectionId,685 TokenId,686 TokenChild,687 PhantomType<(688 TokenData<T::CrossAccountId>,689 RpcCollection<T::AccountId>,690 691 RmrkCollectionInfo<T::AccountId>,692 RmrkInstanceInfo<T::AccountId>,693 RmrkResourceInfo,694 RmrkPropertyInfo,695 RmrkBaseInfo<T::AccountId>,696 RmrkPartType,697 RmrkBoundedTheme,698 RmrkNftChild,699 )>,700 ),701 QueryKind = OptionQuery,702 >;703704 #[pallet::hooks]705 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {706 fn on_runtime_upgrade() -> Weight {707 StorageVersion::new(1).put::<Pallet<T>>();708709 Weight::zero()710 }711 }712}713714impl<T: Config> Pallet<T> {715 716 717 718 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {719 ensure!(720 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,721 <Error<T>>::AddressIsZero722 );723 Ok(())724 }725726 727 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {728 <IsAdmin<T>>::iter_prefix((collection,))729 .map(|(a, _)| a)730 .collect()731 }732733 734 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {735 <Allowlist<T>>::iter_prefix((collection,))736 .map(|(a, _)| a)737 .collect()738 }739740 741 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {742 <Allowlist<T>>::get((collection, user))743 }744745 746 pub fn collection_stats() -> CollectionStats {747 let created = <CreatedCollectionCount<T>>::get();748 let destroyed = <DestroyedCollectionCount<T>>::get();749 CollectionStats {750 created: created.0,751 destroyed: destroyed.0,752 alive: created.0 - destroyed.0,753 }754 }755756 757 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {758 let collection = <CollectionById<T>>::get(collection)?;759 let limits = collection.limits;760 let effective_limits = CollectionLimits {761 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),762 sponsored_data_size: Some(limits.sponsored_data_size()),763 sponsored_data_rate_limit: Some(764 limits765 .sponsored_data_rate_limit766 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),767 ),768 token_limit: Some(limits.token_limit()),769 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(770 match collection.mode {771 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,772 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,773 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,774 },775 )),776 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),777 owner_can_transfer: Some(limits.owner_can_transfer()),778 owner_can_destroy: Some(limits.owner_can_destroy()),779 transfers_enabled: Some(limits.transfers_enabled()),780 };781782 Some(effective_limits)783 }784785 786 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {787 let Collection {788 name,789 description,790 owner,791 mode,792 token_prefix,793 sponsorship,794 limits,795 permissions,796 flags,797 } = <CollectionById<T>>::get(collection)?;798799 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)800 .into_iter()801 .map(|(key, permission)| PropertyKeyPermission { key, permission })802 .collect();803804 let properties = <CollectionProperties<T>>::get(collection)805 .into_iter()806 .map(|(key, value)| Property { key, value })807 .collect();808809 let permissions = CollectionPermissions {810 access: Some(permissions.access()),811 mint_mode: Some(permissions.mint_mode()),812 nesting: Some(permissions.nesting().clone()),813 };814815 Some(RpcCollection {816 name: name.into_inner(),817 description: description.into_inner(),818 owner,819 mode,820 token_prefix: token_prefix.into_inner(),821 sponsorship,822 limits,823 permissions,824 token_property_permissions,825 properties,826 read_only: flags.external,827828 flags: RpcCollectionFlags {829 foreign: flags.foreign,830 erc721metadata: flags.erc721metadata,831 },832 })833 }834}835836macro_rules! limit_default {837 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{838 $(839 if let Some($new) = $new.$field {840 let $old = $old.$field($($arg)?);841 let _ = $new;842 let _ = $old;843 $check844 } else {845 $new.$field = $old.$field846 }847 )*848 }};849}850macro_rules! limit_default_clone {851 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{852 $(853 if let Some($new) = $new.$field.clone() {854 let $old = $old.$field($($arg)?);855 let _ = $new;856 let _ = $old;857 $check858 } else {859 $new.$field = $old.$field.clone()860 }861 )*862 }};863}864865impl<T: Config> Pallet<T> {866 867 868 869 870 871 pub fn init_collection(872 owner: T::CrossAccountId,873 payer: T::CrossAccountId,874 data: CreateCollectionData<T::AccountId>,875 flags: CollectionFlags,876 ) -> Result<CollectionId, DispatchError> {877 {878 ensure!(879 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,880 Error::<T>::CollectionTokenPrefixLimitExceeded881 );882 }883884 let created_count = <CreatedCollectionCount<T>>::get()885 .0886 .checked_add(1)887 .ok_or(ArithmeticError::Overflow)?;888 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;889 let id = CollectionId(created_count);890891 892 ensure!(893 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,894 <Error<T>>::TotalCollectionsLimitExceeded895 );896897 898899 let collection = Collection {900 owner: owner.as_sub().clone(),901 name: data.name,902 mode: data.mode.clone(),903 description: data.description,904 token_prefix: data.token_prefix,905 sponsorship: data906 .pending_sponsor907 .map(SponsorshipState::Unconfirmed)908 .unwrap_or_default(),909 limits: data910 .limits911 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))912 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,913 permissions: data914 .permissions915 .map(|permissions| {916 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)917 })918 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,919 flags,920 };921922 let mut collection_properties = up_data_structs::CollectionProperties::get();923 collection_properties924 .try_set_from_iter(data.properties.into_iter())925 .map_err(<Error<T>>::from)?;926927 CollectionProperties::<T>::insert(id, collection_properties);928929 let mut token_props_permissions = PropertiesPermissionMap::new();930 token_props_permissions931 .try_set_from_iter(data.token_property_permissions.into_iter())932 .map_err(<Error<T>>::from)?;933934 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);935936 937 {938 let mut imbalance =939 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();940 imbalance.subsume(941 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(942 &T::TreasuryAccountId::get(),943 T::CollectionCreationPrice::get(),944 ),945 );946 <T as Config>::Currency::settle(947 payer.as_sub(),948 imbalance,949 WithdrawReasons::TRANSFER,950 ExistenceRequirement::KeepAlive,951 )952 .map_err(|_| Error::<T>::NotSufficientFounds)?;953 }954955 <CreatedCollectionCount<T>>::put(created_count);956 <Pallet<T>>::deposit_event(Event::CollectionCreated(957 id,958 data.mode.id(),959 owner.as_sub().clone(),960 ));961 <PalletEvm<T>>::deposit_log(962 erc::CollectionHelpersEvents::CollectionCreated {963 owner: *owner.as_eth(),964 collection_id: eth::collection_id_to_address(id),965 }966 .to_log(T::ContractAddress::get()),967 );968 <CollectionById<T>>::insert(id, collection);969 Ok(id)970 }971972 973 974 975 976 pub fn destroy_collection(977 collection: CollectionHandle<T>,978 sender: &T::CrossAccountId,979 ) -> DispatchResult {980 ensure!(981 collection.limits.owner_can_destroy(),982 <Error<T>>::NoPermission,983 );984 collection.check_is_owner(sender)?;985986 let destroyed_collections = <DestroyedCollectionCount<T>>::get()987 .0988 .checked_add(1)989 .ok_or(ArithmeticError::Overflow)?;990991 992993 <DestroyedCollectionCount<T>>::put(destroyed_collections);994 <CollectionById<T>>::remove(collection.id);995 <AdminAmount<T>>::remove(collection.id);996 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);997 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);998 <CollectionProperties<T>>::remove(collection.id);9991000 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));10011002 <PalletEvm<T>>::deposit_log(1003 erc::CollectionHelpersEvents::CollectionDestroyed {1004 collection_id: eth::collection_id_to_address(collection.id),1005 }1006 .to_log(T::ContractAddress::get()),1007 );1008 Ok(())1009 }10101011 1012 1013 1014 1015 1016 pub fn set_collection_property(1017 collection: &CollectionHandle<T>,1018 sender: &T::CrossAccountId,1019 property: Property,1020 ) -> DispatchResult {1021 collection.check_is_owner_or_admin(sender)?;10221023 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1024 let property = property.clone();1025 properties.try_set(property.key, property.value)1026 })1027 .map_err(<Error<T>>::from)?;10281029 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10301031 Ok(())1032 }10331034 1035 1036 1037 1038 1039 pub fn set_scoped_collection_property(1040 collection_id: CollectionId,1041 scope: PropertyScope,1042 property: Property,1043 ) -> DispatchResult {1044 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1045 properties.try_scoped_set(scope, property.key, property.value)1046 })1047 .map_err(<Error<T>>::from)?;10481049 Ok(())1050 }10511052 1053 1054 1055 1056 1057 pub fn set_scoped_collection_properties(1058 collection_id: CollectionId,1059 scope: PropertyScope,1060 properties: impl Iterator<Item = Property>,1061 ) -> DispatchResult {1062 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1063 stored_properties.try_scoped_set_from_iter(scope, properties)1064 })1065 .map_err(<Error<T>>::from)?;10661067 Ok(())1068 }10691070 1071 1072 1073 1074 1075 #[transactional]1076 pub fn set_collection_properties(1077 collection: &CollectionHandle<T>,1078 sender: &T::CrossAccountId,1079 properties: Vec<Property>,1080 ) -> DispatchResult {1081 for property in properties {1082 Self::set_collection_property(collection, sender, property)?;1083 }10841085 Ok(())1086 }10871088 1089 1090 1091 1092 1093 pub fn delete_collection_property(1094 collection: &CollectionHandle<T>,1095 sender: &T::CrossAccountId,1096 property_key: PropertyKey,1097 ) -> DispatchResult {1098 collection.check_is_owner_or_admin(sender)?;10991100 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1101 properties.remove(&property_key)1102 })1103 .map_err(<Error<T>>::from)?;11041105 Self::deposit_event(Event::CollectionPropertyDeleted(1106 collection.id,1107 property_key,1108 ));11091110 Ok(())1111 }11121113 1114 1115 1116 1117 1118 #[transactional]1119 pub fn delete_collection_properties(1120 collection: &CollectionHandle<T>,1121 sender: &T::CrossAccountId,1122 property_keys: Vec<PropertyKey>,1123 ) -> DispatchResult {1124 for key in property_keys {1125 Self::delete_collection_property(collection, sender, key)?;1126 }11271128 Ok(())1129 }11301131 1132 1133 1134 1135 1136 1137 pub fn set_property_permission_unchecked(1138 collection: CollectionId,1139 property_permission: PropertyKeyPermission,1140 ) -> DispatchResult {1141 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1142 permissions.try_set(property_permission.key, property_permission.permission)1143 })1144 .map_err(<Error<T>>::from)?;1145 Ok(())1146 }11471148 1149 1150 1151 1152 1153 pub fn set_property_permission(1154 collection: &CollectionHandle<T>,1155 sender: &T::CrossAccountId,1156 property_permission: PropertyKeyPermission,1157 ) -> DispatchResult {1158 Self::set_scoped_property_permission(1159 collection,1160 sender,1161 PropertyScope::None,1162 property_permission,1163 )1164 }11651166 1167 1168 1169 1170 1171 1172 pub fn set_scoped_property_permission(1173 collection: &CollectionHandle<T>,1174 sender: &T::CrossAccountId,1175 scope: PropertyScope,1176 property_permission: PropertyKeyPermission,1177 ) -> DispatchResult {1178 collection.check_is_owner_or_admin(sender)?;11791180 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1181 let current_permission = all_permissions.get(&property_permission.key);1182 if matches![1183 current_permission,1184 Some(PropertyPermission { mutable: false, .. })1185 ] {1186 return Err(<Error<T>>::NoPermission.into());1187 }11881189 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1190 let property_permission = property_permission.clone();1191 permissions.try_scoped_set(1192 scope,1193 property_permission.key,1194 property_permission.permission,1195 )1196 })1197 .map_err(<Error<T>>::from)?;11981199 Self::deposit_event(Event::PropertyPermissionSet(1200 collection.id,1201 property_permission.key,1202 ));12031204 Ok(())1205 }12061207 1208 1209 1210 1211 1212 #[transactional]1213 pub fn set_token_property_permissions(1214 collection: &CollectionHandle<T>,1215 sender: &T::CrossAccountId,1216 property_permissions: Vec<PropertyKeyPermission>,1217 ) -> DispatchResult {1218 Self::set_scoped_token_property_permissions(1219 collection,1220 sender,1221 PropertyScope::None,1222 property_permissions,1223 )1224 }12251226 1227 1228 1229 1230 1231 1232 #[transactional]1233 pub fn set_scoped_token_property_permissions(1234 collection: &CollectionHandle<T>,1235 sender: &T::CrossAccountId,1236 scope: PropertyScope,1237 property_permissions: Vec<PropertyKeyPermission>,1238 ) -> DispatchResult {1239 for prop_pemission in property_permissions {1240 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1241 }12421243 Ok(())1244 }12451246 1247 pub fn get_collection_property(1248 collection_id: CollectionId,1249 key: &PropertyKey,1250 ) -> Option<PropertyValue> {1251 Self::collection_properties(collection_id).get(key).cloned()1252 }12531254 1255 pub fn bytes_keys_to_property_keys(1256 keys: Vec<Vec<u8>>,1257 ) -> Result<Vec<PropertyKey>, DispatchError> {1258 keys.into_iter()1259 .map(|key| -> Result<PropertyKey, DispatchError> {1260 key.try_into()1261 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1262 })1263 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1264 }12651266 1267 pub fn filter_collection_properties(1268 collection_id: CollectionId,1269 keys: Option<Vec<PropertyKey>>,1270 ) -> Result<Vec<Property>, DispatchError> {1271 let properties = Self::collection_properties(collection_id);12721273 let properties = keys1274 .map(|keys| {1275 keys.into_iter()1276 .filter_map(|key| {1277 properties.get(&key).map(|value| Property {1278 key,1279 value: value.clone(),1280 })1281 })1282 .collect()1283 })1284 .unwrap_or_else(|| {1285 properties1286 .into_iter()1287 .map(|(key, value)| Property { key, value })1288 .collect()1289 });12901291 Ok(properties)1292 }12931294 1295 pub fn filter_property_permissions(1296 collection_id: CollectionId,1297 keys: Option<Vec<PropertyKey>>,1298 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1299 let permissions = Self::property_permissions(collection_id);13001301 let key_permissions = keys1302 .map(|keys| {1303 keys.into_iter()1304 .filter_map(|key| {1305 permissions1306 .get(&key)1307 .map(|permission| PropertyKeyPermission {1308 key,1309 permission: permission.clone(),1310 })1311 })1312 .collect()1313 })1314 .unwrap_or_else(|| {1315 permissions1316 .into_iter()1317 .map(|(key, permission)| PropertyKeyPermission { key, permission })1318 .collect()1319 });13201321 Ok(key_permissions)1322 }13231324 1325 1326 1327 pub fn toggle_allowlist(1328 collection: &CollectionHandle<T>,1329 sender: &T::CrossAccountId,1330 user: &T::CrossAccountId,1331 allowed: bool,1332 ) -> DispatchResult {1333 collection.check_is_owner_or_admin(sender)?;13341335 13361337 if allowed {1338 <Allowlist<T>>::insert((collection.id, user), true);1339 } else {1340 <Allowlist<T>>::remove((collection.id, user));1341 }13421343 Ok(())1344 }13451346 1347 1348 1349 pub fn toggle_admin(1350 collection: &CollectionHandle<T>,1351 sender: &T::CrossAccountId,1352 user: &T::CrossAccountId,1353 admin: bool,1354 ) -> DispatchResult {1355 collection.check_is_owner(sender)?;13561357 let was_admin = <IsAdmin<T>>::get((collection.id, user));1358 if was_admin == admin {1359 return Ok(());1360 }1361 let amount = <AdminAmount<T>>::get(collection.id);13621363 if admin {1364 let amount = amount1365 .checked_add(1)1366 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1367 ensure!(1368 amount <= Self::collection_admins_limit(),1369 <Error<T>>::CollectionAdminCountExceeded,1370 );13711372 13731374 <AdminAmount<T>>::insert(collection.id, amount);1375 <IsAdmin<T>>::insert((collection.id, user), true);1376 } else {1377 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1378 <IsAdmin<T>>::remove((collection.id, user));1379 }13801381 Ok(())1382 }13831384 1385 pub fn clamp_limits(1386 mode: CollectionMode,1387 old_limit: &CollectionLimits,1388 mut new_limit: CollectionLimits,1389 ) -> Result<CollectionLimits, DispatchError> {1390 let limits = old_limit;1391 limit_default!(old_limit, new_limit,1392 account_token_ownership_limit => ensure!(1393 new_limit <= MAX_TOKEN_OWNERSHIP,1394 <Error<T>>::CollectionLimitBoundsExceeded,1395 ),1396 sponsored_data_size => ensure!(1397 new_limit <= CUSTOM_DATA_LIMIT,1398 <Error<T>>::CollectionLimitBoundsExceeded,1399 ),14001401 sponsored_data_rate_limit => {},1402 token_limit => ensure!(1403 old_limit >= new_limit && new_limit > 0,1404 <Error<T>>::CollectionTokenLimitExceeded1405 ),14061407 sponsor_transfer_timeout(match mode {1408 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1409 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1410 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1411 }) => ensure!(1412 new_limit <= MAX_SPONSOR_TIMEOUT,1413 <Error<T>>::CollectionLimitBoundsExceeded,1414 ),1415 sponsor_approve_timeout => {},1416 owner_can_transfer => ensure!(1417 !limits.owner_can_transfer_instaled() ||1418 old_limit || !new_limit,1419 <Error<T>>::OwnerPermissionsCantBeReverted,1420 ),1421 owner_can_destroy => ensure!(1422 old_limit || !new_limit,1423 <Error<T>>::OwnerPermissionsCantBeReverted,1424 ),1425 transfers_enabled => {},1426 );1427 Ok(new_limit)1428 }14291430 1431 pub fn clamp_permissions(1432 _mode: CollectionMode,1433 old_permission: &CollectionPermissions,1434 mut new_permission: CollectionPermissions,1435 ) -> Result<CollectionPermissions, DispatchError> {1436 limit_default_clone!(old_permission, new_permission,1437 access => {},1438 mint_mode => {},1439 nesting => { },1440 );1441 Ok(new_permission)1442 }1443}144414451446#[macro_export]1447macro_rules! unsupported {1448 ($runtime:path) => {1449 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1450 };1451}145214531454pub trait CommonWeightInfo<CrossAccountId> {1455 1456 fn create_item() -> Weight;14571458 1459 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14601461 1462 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14631464 1465 fn burn_item() -> Weight;14661467 1468 1469 1470 fn set_collection_properties(amount: u32) -> Weight;14711472 1473 1474 1475 fn delete_collection_properties(amount: u32) -> Weight;14761477 1478 1479 1480 fn set_token_properties(amount: u32) -> Weight;14811482 1483 1484 1485 fn delete_token_properties(amount: u32) -> Weight;14861487 1488 1489 1490 fn set_token_property_permissions(amount: u32) -> Weight;14911492 1493 fn transfer() -> Weight;14941495 1496 fn approve() -> Weight;14971498 1499 fn transfer_from() -> Weight;15001501 1502 fn burn_from() -> Weight;15031504 1505 1506 1507 1508 fn burn_recursively_self_raw() -> Weight;15091510 1511 1512 1513 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15141515 1516 1517 1518 1519 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1520 Self::burn_recursively_self_raw()1521 .saturating_mul(max_selfs.max(1) as u64)1522 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1523 }15241525 1526 fn token_owner() -> Weight;1527}152815291530pub trait RefungibleExtensionsWeightInfo {1531 1532 fn repartition() -> Weight;1533}153415351536153715381539pub trait CommonCollectionOperations<T: Config> {1540 1541 1542 1543 1544 1545 1546 fn create_item(1547 &self,1548 sender: T::CrossAccountId,1549 to: T::CrossAccountId,1550 data: CreateItemData,1551 nesting_budget: &dyn Budget,1552 ) -> DispatchResultWithPostInfo;15531554 1555 1556 1557 1558 1559 1560 fn create_multiple_items(1561 &self,1562 sender: T::CrossAccountId,1563 to: T::CrossAccountId,1564 data: Vec<CreateItemData>,1565 nesting_budget: &dyn Budget,1566 ) -> DispatchResultWithPostInfo;15671568 1569 1570 1571 1572 1573 1574 fn create_multiple_items_ex(1575 &self,1576 sender: T::CrossAccountId,1577 data: CreateItemExData<T::CrossAccountId>,1578 nesting_budget: &dyn Budget,1579 ) -> DispatchResultWithPostInfo;15801581 1582 1583 1584 1585 1586 fn burn_item(1587 &self,1588 sender: T::CrossAccountId,1589 token: TokenId,1590 amount: u128,1591 ) -> DispatchResultWithPostInfo;15921593 1594 1595 1596 1597 1598 1599 fn burn_item_recursively(1600 &self,1601 sender: T::CrossAccountId,1602 token: TokenId,1603 self_budget: &dyn Budget,1604 breadth_budget: &dyn Budget,1605 ) -> DispatchResultWithPostInfo;16061607 1608 1609 1610 1611 fn set_collection_properties(1612 &self,1613 sender: T::CrossAccountId,1614 properties: Vec<Property>,1615 ) -> DispatchResultWithPostInfo;16161617 1618 1619 1620 1621 fn delete_collection_properties(1622 &self,1623 sender: &T::CrossAccountId,1624 property_keys: Vec<PropertyKey>,1625 ) -> DispatchResultWithPostInfo;16261627 1628 1629 1630 1631 1632 1633 1634 1635 1636 fn set_token_properties(1637 &self,1638 sender: T::CrossAccountId,1639 token_id: TokenId,1640 properties: Vec<Property>,1641 budget: &dyn Budget,1642 ) -> DispatchResultWithPostInfo;16431644 1645 1646 1647 1648 1649 1650 1651 1652 1653 fn delete_token_properties(1654 &self,1655 sender: T::CrossAccountId,1656 token_id: TokenId,1657 property_keys: Vec<PropertyKey>,1658 budget: &dyn Budget,1659 ) -> DispatchResultWithPostInfo;16601661 1662 1663 1664 1665 1666 1667 fn set_token_property_permissions(1668 &self,1669 sender: &T::CrossAccountId,1670 property_permissions: Vec<PropertyKeyPermission>,1671 ) -> DispatchResultWithPostInfo;16721673 1674 1675 1676 1677 1678 1679 1680 fn transfer(1681 &self,1682 sender: T::CrossAccountId,1683 to: T::CrossAccountId,1684 token: TokenId,1685 amount: u128,1686 budget: &dyn Budget,1687 ) -> DispatchResultWithPostInfo;16881689 1690 1691 1692 1693 1694 1695 fn approve(1696 &self,1697 sender: T::CrossAccountId,1698 spender: T::CrossAccountId,1699 token: TokenId,1700 amount: u128,1701 ) -> DispatchResultWithPostInfo;17021703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 fn transfer_from(1714 &self,1715 sender: T::CrossAccountId,1716 from: T::CrossAccountId,1717 to: T::CrossAccountId,1718 token: TokenId,1719 amount: u128,1720 budget: &dyn Budget,1721 ) -> DispatchResultWithPostInfo;17221723 1724 1725 1726 1727 1728 1729 1730 1731 1732 fn burn_from(1733 &self,1734 sender: T::CrossAccountId,1735 from: T::CrossAccountId,1736 token: TokenId,1737 amount: u128,1738 budget: &dyn Budget,1739 ) -> DispatchResultWithPostInfo;17401741 1742 1743 1744 1745 1746 1747 fn check_nesting(1748 &self,1749 sender: T::CrossAccountId,1750 from: (CollectionId, TokenId),1751 under: TokenId,1752 budget: &dyn Budget,1753 ) -> DispatchResult;17541755 1756 1757 1758 1759 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17601761 1762 1763 1764 1765 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17661767 1768 1769 1770 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17711772 1773 fn collection_tokens(&self) -> Vec<TokenId>;17741775 1776 1777 1778 fn token_exists(&self, token: TokenId) -> bool;17791780 1781 fn last_token_id(&self) -> TokenId;17821783 1784 1785 1786 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17871788 1789 1790 1791 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17921793 1794 1795 1796 1797 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17981799 1800 1801 1802 1803 1804 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18051806 1807 fn total_supply(&self) -> u32;18081809 1810 1811 1812 fn account_balance(&self, account: T::CrossAccountId) -> u32;18131814 1815 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18161817 1818 fn total_pieces(&self, token: TokenId) -> Option<u128>;18191820 1821 1822 1823 1824 1825 fn allowance(1826 &self,1827 sender: T::CrossAccountId,1828 spender: T::CrossAccountId,1829 token: TokenId,1830 ) -> u128;18311832 1833 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1834}183518361837pub trait RefungibleExtensions<T>1838where1839 T: Config,1840{1841 1842 1843 1844 1845 1846 1847 1848 fn repartition(1849 &self,1850 sender: &T::CrossAccountId,1851 token: TokenId,1852 amount: u128,1853 ) -> DispatchResultWithPostInfo;1854}18551856185718581859pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1860 let post_info = PostDispatchInfo {1861 actual_weight: Some(weight),1862 pays_fee: Pays::Yes,1863 };1864 match res {1865 Ok(()) => Ok(post_info),1866 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1867 }1868}18691870impl<T: Config> From<PropertiesError> for Error<T> {1871 fn from(error: PropertiesError) -> Self {1872 match error {1873 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1874 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1875 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1876 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1877 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1878 }1879 }1880}