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 ApprovedForAll(477 478 CollectionId,479 480 T::CrossAccountId,481 482 T::CrossAccountId,483 484 bool,485 ),486487 488 CollectionPropertySet(489 490 CollectionId,491 492 PropertyKey,493 ),494495 496 CollectionPropertyDeleted(497 498 CollectionId,499 500 PropertyKey,501 ),502503 504 TokenPropertySet(505 506 CollectionId,507 508 TokenId,509 510 PropertyKey,511 ),512513 514 TokenPropertyDeleted(515 516 CollectionId,517 518 TokenId,519 520 PropertyKey,521 ),522523 524 PropertyPermissionSet(525 526 CollectionId,527 528 PropertyKey,529 ),530 }531532 #[pallet::error]533 pub enum Error<T> {534 535 CollectionNotFound,536 537 MustBeTokenOwner,538 539 NoPermission,540 541 CantDestroyNotEmptyCollection,542 543 PublicMintingNotAllowed,544 545 AddressNotInAllowlist,546547 548 CollectionNameLimitExceeded,549 550 CollectionDescriptionLimitExceeded,551 552 CollectionTokenPrefixLimitExceeded,553 554 TotalCollectionsLimitExceeded,555 556 CollectionAdminCountExceeded,557 558 CollectionLimitBoundsExceeded,559 560 OwnerPermissionsCantBeReverted,561 562 TransferNotAllowed,563 564 AccountTokenLimitExceeded,565 566 CollectionTokenLimitExceeded,567 568 MetadataFlagFrozen,569570 571 TokenNotFound,572 573 TokenValueTooLow,574 575 ApprovedValueTooLow,576 577 CantApproveMoreThanOwned,578579 580 AddressIsZero,581582 583 UnsupportedOperation,584585 586 NotSufficientFounds,587588 589 UserIsNotAllowedToNest,590 591 SourceCollectionIsNotAllowedToNest,592593 594 CollectionFieldSizeExceeded,595596 597 NoSpaceForProperty,598599 600 PropertyLimitReached,601602 603 PropertyKeyIsTooLong,604605 606 InvalidCharacterInPropertyKey,607608 609 EmptyPropertyKey,610611 612 CollectionIsExternal,613614 615 CollectionIsInternal,616 }617618 619 #[pallet::storage]620 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;621622 623 #[pallet::storage]624 pub type DestroyedCollectionCount<T> =625 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;626627 628 #[pallet::storage]629 pub type CollectionById<T> = StorageMap<630 Hasher = Blake2_128Concat,631 Key = CollectionId,632 Value = Collection<<T as frame_system::Config>::AccountId>,633 QueryKind = OptionQuery,634 >;635636 637 #[pallet::storage]638 #[pallet::getter(fn collection_properties)]639 pub type CollectionProperties<T> = StorageMap<640 Hasher = Blake2_128Concat,641 Key = CollectionId,642 Value = Properties,643 QueryKind = ValueQuery,644 OnEmpty = up_data_structs::CollectionProperties,645 >;646647 648 #[pallet::storage]649 #[pallet::getter(fn property_permissions)]650 pub type CollectionPropertyPermissions<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = PropertiesPermissionMap,654 QueryKind = ValueQuery,655 >;656657 658 #[pallet::storage]659 pub type AdminAmount<T> = StorageMap<660 Hasher = Blake2_128Concat,661 Key = CollectionId,662 Value = u32,663 QueryKind = ValueQuery,664 >;665666 667 #[pallet::storage]668 pub type IsAdmin<T: Config> = StorageNMap<669 Key = (670 Key<Blake2_128Concat, CollectionId>,671 Key<Blake2_128Concat, T::CrossAccountId>,672 ),673 Value = bool,674 QueryKind = ValueQuery,675 >;676677 678 #[pallet::storage]679 pub type Allowlist<T: Config> = StorageNMap<680 Key = (681 Key<Blake2_128Concat, CollectionId>,682 Key<Blake2_128Concat, T::CrossAccountId>,683 ),684 Value = bool,685 QueryKind = ValueQuery,686 >;687688 689 #[pallet::storage]690 pub type DummyStorageValue<T: Config> = StorageValue<691 Value = (692 CollectionStats,693 CollectionId,694 TokenId,695 TokenChild,696 PhantomType<(697 TokenData<T::CrossAccountId>,698 RpcCollection<T::AccountId>,699 700 RmrkCollectionInfo<T::AccountId>,701 RmrkInstanceInfo<T::AccountId>,702 RmrkResourceInfo,703 RmrkPropertyInfo,704 RmrkBaseInfo<T::AccountId>,705 RmrkPartType,706 RmrkBoundedTheme,707 RmrkNftChild,708 )>,709 ),710 QueryKind = OptionQuery,711 >;712713 #[pallet::hooks]714 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {715 fn on_runtime_upgrade() -> Weight {716 StorageVersion::new(1).put::<Pallet<T>>();717718 Weight::zero()719 }720 }721}722723impl<T: Config> Pallet<T> {724 725 726 727 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {728 ensure!(729 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,730 <Error<T>>::AddressIsZero731 );732 Ok(())733 }734735 736 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {737 <IsAdmin<T>>::iter_prefix((collection,))738 .map(|(a, _)| a)739 .collect()740 }741742 743 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {744 <Allowlist<T>>::iter_prefix((collection,))745 .map(|(a, _)| a)746 .collect()747 }748749 750 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {751 <Allowlist<T>>::get((collection, user))752 }753754 755 pub fn collection_stats() -> CollectionStats {756 let created = <CreatedCollectionCount<T>>::get();757 let destroyed = <DestroyedCollectionCount<T>>::get();758 CollectionStats {759 created: created.0,760 destroyed: destroyed.0,761 alive: created.0 - destroyed.0,762 }763 }764765 766 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {767 let collection = <CollectionById<T>>::get(collection)?;768 let limits = collection.limits;769 let effective_limits = CollectionLimits {770 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),771 sponsored_data_size: Some(limits.sponsored_data_size()),772 sponsored_data_rate_limit: Some(773 limits774 .sponsored_data_rate_limit775 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),776 ),777 token_limit: Some(limits.token_limit()),778 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(779 match collection.mode {780 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,781 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,782 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,783 },784 )),785 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),786 owner_can_transfer: Some(limits.owner_can_transfer()),787 owner_can_destroy: Some(limits.owner_can_destroy()),788 transfers_enabled: Some(limits.transfers_enabled()),789 };790791 Some(effective_limits)792 }793794 795 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {796 let Collection {797 name,798 description,799 owner,800 mode,801 token_prefix,802 sponsorship,803 limits,804 permissions,805 flags,806 } = <CollectionById<T>>::get(collection)?;807808 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)809 .into_iter()810 .map(|(key, permission)| PropertyKeyPermission { key, permission })811 .collect();812813 let properties = <CollectionProperties<T>>::get(collection)814 .into_iter()815 .map(|(key, value)| Property { key, value })816 .collect();817818 let permissions = CollectionPermissions {819 access: Some(permissions.access()),820 mint_mode: Some(permissions.mint_mode()),821 nesting: Some(permissions.nesting().clone()),822 };823824 Some(RpcCollection {825 name: name.into_inner(),826 description: description.into_inner(),827 owner,828 mode,829 token_prefix: token_prefix.into_inner(),830 sponsorship,831 limits,832 permissions,833 token_property_permissions,834 properties,835 read_only: flags.external,836837 flags: RpcCollectionFlags {838 foreign: flags.foreign,839 erc721metadata: flags.erc721metadata,840 },841 })842 }843}844845macro_rules! limit_default {846 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{847 $(848 if let Some($new) = $new.$field {849 let $old = $old.$field($($arg)?);850 let _ = $new;851 let _ = $old;852 $check853 } else {854 $new.$field = $old.$field855 }856 )*857 }};858}859macro_rules! limit_default_clone {860 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{861 $(862 if let Some($new) = $new.$field.clone() {863 let $old = $old.$field($($arg)?);864 let _ = $new;865 let _ = $old;866 $check867 } else {868 $new.$field = $old.$field.clone()869 }870 )*871 }};872}873874impl<T: Config> Pallet<T> {875 876 877 878 879 880 pub fn init_collection(881 owner: T::CrossAccountId,882 payer: T::CrossAccountId,883 data: CreateCollectionData<T::AccountId>,884 flags: CollectionFlags,885 ) -> Result<CollectionId, DispatchError> {886 {887 ensure!(888 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,889 Error::<T>::CollectionTokenPrefixLimitExceeded890 );891 }892893 let created_count = <CreatedCollectionCount<T>>::get()894 .0895 .checked_add(1)896 .ok_or(ArithmeticError::Overflow)?;897 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;898 let id = CollectionId(created_count);899900 901 ensure!(902 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,903 <Error<T>>::TotalCollectionsLimitExceeded904 );905906 907908 let collection = Collection {909 owner: owner.as_sub().clone(),910 name: data.name,911 mode: data.mode.clone(),912 description: data.description,913 token_prefix: data.token_prefix,914 sponsorship: data915 .pending_sponsor916 .map(SponsorshipState::Unconfirmed)917 .unwrap_or_default(),918 limits: data919 .limits920 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))921 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,922 permissions: data923 .permissions924 .map(|permissions| {925 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)926 })927 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,928 flags,929 };930931 let mut collection_properties = up_data_structs::CollectionProperties::get();932 collection_properties933 .try_set_from_iter(data.properties.into_iter())934 .map_err(<Error<T>>::from)?;935936 CollectionProperties::<T>::insert(id, collection_properties);937938 let mut token_props_permissions = PropertiesPermissionMap::new();939 token_props_permissions940 .try_set_from_iter(data.token_property_permissions.into_iter())941 .map_err(<Error<T>>::from)?;942943 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);944945 946 {947 let mut imbalance =948 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();949 imbalance.subsume(950 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(951 &T::TreasuryAccountId::get(),952 T::CollectionCreationPrice::get(),953 ),954 );955 <T as Config>::Currency::settle(956 payer.as_sub(),957 imbalance,958 WithdrawReasons::TRANSFER,959 ExistenceRequirement::KeepAlive,960 )961 .map_err(|_| Error::<T>::NotSufficientFounds)?;962 }963964 <CreatedCollectionCount<T>>::put(created_count);965 <Pallet<T>>::deposit_event(Event::CollectionCreated(966 id,967 data.mode.id(),968 owner.as_sub().clone(),969 ));970 <PalletEvm<T>>::deposit_log(971 erc::CollectionHelpersEvents::CollectionCreated {972 owner: *owner.as_eth(),973 collection_id: eth::collection_id_to_address(id),974 }975 .to_log(T::ContractAddress::get()),976 );977 <CollectionById<T>>::insert(id, collection);978 Ok(id)979 }980981 982 983 984 985 pub fn destroy_collection(986 collection: CollectionHandle<T>,987 sender: &T::CrossAccountId,988 ) -> DispatchResult {989 ensure!(990 collection.limits.owner_can_destroy(),991 <Error<T>>::NoPermission,992 );993 collection.check_is_owner(sender)?;994995 let destroyed_collections = <DestroyedCollectionCount<T>>::get()996 .0997 .checked_add(1)998 .ok_or(ArithmeticError::Overflow)?;9991000 10011002 <DestroyedCollectionCount<T>>::put(destroyed_collections);1003 <CollectionById<T>>::remove(collection.id);1004 <AdminAmount<T>>::remove(collection.id);1005 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1006 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1007 <CollectionProperties<T>>::remove(collection.id);10081009 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));10101011 <PalletEvm<T>>::deposit_log(1012 erc::CollectionHelpersEvents::CollectionDestroyed {1013 collection_id: eth::collection_id_to_address(collection.id),1014 }1015 .to_log(T::ContractAddress::get()),1016 );1017 Ok(())1018 }10191020 1021 1022 1023 1024 1025 pub fn set_collection_property(1026 collection: &CollectionHandle<T>,1027 sender: &T::CrossAccountId,1028 property: Property,1029 ) -> DispatchResult {1030 collection.check_is_owner_or_admin(sender)?;10311032 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1033 let property = property.clone();1034 properties.try_set(property.key, property.value)1035 })1036 .map_err(<Error<T>>::from)?;10371038 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10391040 Ok(())1041 }10421043 1044 1045 1046 1047 1048 pub fn set_scoped_collection_property(1049 collection_id: CollectionId,1050 scope: PropertyScope,1051 property: Property,1052 ) -> DispatchResult {1053 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1054 properties.try_scoped_set(scope, property.key, property.value)1055 })1056 .map_err(<Error<T>>::from)?;10571058 Ok(())1059 }10601061 1062 1063 1064 1065 1066 pub fn set_scoped_collection_properties(1067 collection_id: CollectionId,1068 scope: PropertyScope,1069 properties: impl Iterator<Item = Property>,1070 ) -> DispatchResult {1071 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1072 stored_properties.try_scoped_set_from_iter(scope, properties)1073 })1074 .map_err(<Error<T>>::from)?;10751076 Ok(())1077 }10781079 1080 1081 1082 1083 1084 #[transactional]1085 pub fn set_collection_properties(1086 collection: &CollectionHandle<T>,1087 sender: &T::CrossAccountId,1088 properties: Vec<Property>,1089 ) -> DispatchResult {1090 for property in properties {1091 Self::set_collection_property(collection, sender, property)?;1092 }10931094 Ok(())1095 }10961097 1098 1099 1100 1101 1102 pub fn delete_collection_property(1103 collection: &CollectionHandle<T>,1104 sender: &T::CrossAccountId,1105 property_key: PropertyKey,1106 ) -> DispatchResult {1107 collection.check_is_owner_or_admin(sender)?;11081109 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1110 properties.remove(&property_key)1111 })1112 .map_err(<Error<T>>::from)?;11131114 Self::deposit_event(Event::CollectionPropertyDeleted(1115 collection.id,1116 property_key,1117 ));11181119 Ok(())1120 }11211122 1123 1124 1125 1126 1127 #[transactional]1128 pub fn delete_collection_properties(1129 collection: &CollectionHandle<T>,1130 sender: &T::CrossAccountId,1131 property_keys: Vec<PropertyKey>,1132 ) -> DispatchResult {1133 for key in property_keys {1134 Self::delete_collection_property(collection, sender, key)?;1135 }11361137 Ok(())1138 }11391140 1141 1142 1143 1144 1145 1146 pub fn set_property_permission_unchecked(1147 collection: CollectionId,1148 property_permission: PropertyKeyPermission,1149 ) -> DispatchResult {1150 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1151 permissions.try_set(property_permission.key, property_permission.permission)1152 })1153 .map_err(<Error<T>>::from)?;1154 Ok(())1155 }11561157 1158 1159 1160 1161 1162 pub fn set_property_permission(1163 collection: &CollectionHandle<T>,1164 sender: &T::CrossAccountId,1165 property_permission: PropertyKeyPermission,1166 ) -> DispatchResult {1167 Self::set_scoped_property_permission(1168 collection,1169 sender,1170 PropertyScope::None,1171 property_permission,1172 )1173 }11741175 1176 1177 1178 1179 1180 1181 pub fn set_scoped_property_permission(1182 collection: &CollectionHandle<T>,1183 sender: &T::CrossAccountId,1184 scope: PropertyScope,1185 property_permission: PropertyKeyPermission,1186 ) -> DispatchResult {1187 collection.check_is_owner_or_admin(sender)?;11881189 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1190 let current_permission = all_permissions.get(&property_permission.key);1191 if matches![1192 current_permission,1193 Some(PropertyPermission { mutable: false, .. })1194 ] {1195 return Err(<Error<T>>::NoPermission.into());1196 }11971198 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1199 let property_permission = property_permission.clone();1200 permissions.try_scoped_set(1201 scope,1202 property_permission.key,1203 property_permission.permission,1204 )1205 })1206 .map_err(<Error<T>>::from)?;12071208 Self::deposit_event(Event::PropertyPermissionSet(1209 collection.id,1210 property_permission.key,1211 ));12121213 Ok(())1214 }12151216 1217 1218 1219 1220 1221 #[transactional]1222 pub fn set_token_property_permissions(1223 collection: &CollectionHandle<T>,1224 sender: &T::CrossAccountId,1225 property_permissions: Vec<PropertyKeyPermission>,1226 ) -> DispatchResult {1227 Self::set_scoped_token_property_permissions(1228 collection,1229 sender,1230 PropertyScope::None,1231 property_permissions,1232 )1233 }12341235 1236 1237 1238 1239 1240 1241 #[transactional]1242 pub fn set_scoped_token_property_permissions(1243 collection: &CollectionHandle<T>,1244 sender: &T::CrossAccountId,1245 scope: PropertyScope,1246 property_permissions: Vec<PropertyKeyPermission>,1247 ) -> DispatchResult {1248 for prop_pemission in property_permissions {1249 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1250 }12511252 Ok(())1253 }12541255 1256 pub fn get_collection_property(1257 collection_id: CollectionId,1258 key: &PropertyKey,1259 ) -> Option<PropertyValue> {1260 Self::collection_properties(collection_id).get(key).cloned()1261 }12621263 1264 pub fn bytes_keys_to_property_keys(1265 keys: Vec<Vec<u8>>,1266 ) -> Result<Vec<PropertyKey>, DispatchError> {1267 keys.into_iter()1268 .map(|key| -> Result<PropertyKey, DispatchError> {1269 key.try_into()1270 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1271 })1272 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1273 }12741275 1276 pub fn filter_collection_properties(1277 collection_id: CollectionId,1278 keys: Option<Vec<PropertyKey>>,1279 ) -> Result<Vec<Property>, DispatchError> {1280 let properties = Self::collection_properties(collection_id);12811282 let properties = keys1283 .map(|keys| {1284 keys.into_iter()1285 .filter_map(|key| {1286 properties.get(&key).map(|value| Property {1287 key,1288 value: value.clone(),1289 })1290 })1291 .collect()1292 })1293 .unwrap_or_else(|| {1294 properties1295 .into_iter()1296 .map(|(key, value)| Property { key, value })1297 .collect()1298 });12991300 Ok(properties)1301 }13021303 1304 pub fn filter_property_permissions(1305 collection_id: CollectionId,1306 keys: Option<Vec<PropertyKey>>,1307 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1308 let permissions = Self::property_permissions(collection_id);13091310 let key_permissions = keys1311 .map(|keys| {1312 keys.into_iter()1313 .filter_map(|key| {1314 permissions1315 .get(&key)1316 .map(|permission| PropertyKeyPermission {1317 key,1318 permission: permission.clone(),1319 })1320 })1321 .collect()1322 })1323 .unwrap_or_else(|| {1324 permissions1325 .into_iter()1326 .map(|(key, permission)| PropertyKeyPermission { key, permission })1327 .collect()1328 });13291330 Ok(key_permissions)1331 }13321333 1334 1335 1336 pub fn toggle_allowlist(1337 collection: &CollectionHandle<T>,1338 sender: &T::CrossAccountId,1339 user: &T::CrossAccountId,1340 allowed: bool,1341 ) -> DispatchResult {1342 collection.check_is_owner_or_admin(sender)?;13431344 13451346 if allowed {1347 <Allowlist<T>>::insert((collection.id, user), true);1348 } else {1349 <Allowlist<T>>::remove((collection.id, user));1350 }13511352 Ok(())1353 }13541355 1356 1357 1358 pub fn toggle_admin(1359 collection: &CollectionHandle<T>,1360 sender: &T::CrossAccountId,1361 user: &T::CrossAccountId,1362 admin: bool,1363 ) -> DispatchResult {1364 collection.check_is_owner(sender)?;13651366 let was_admin = <IsAdmin<T>>::get((collection.id, user));1367 if was_admin == admin {1368 return Ok(());1369 }1370 let amount = <AdminAmount<T>>::get(collection.id);13711372 if admin {1373 let amount = amount1374 .checked_add(1)1375 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1376 ensure!(1377 amount <= Self::collection_admins_limit(),1378 <Error<T>>::CollectionAdminCountExceeded,1379 );13801381 13821383 <AdminAmount<T>>::insert(collection.id, amount);1384 <IsAdmin<T>>::insert((collection.id, user), true);1385 } else {1386 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1387 <IsAdmin<T>>::remove((collection.id, user));1388 }13891390 Ok(())1391 }13921393 1394 pub fn clamp_limits(1395 mode: CollectionMode,1396 old_limit: &CollectionLimits,1397 mut new_limit: CollectionLimits,1398 ) -> Result<CollectionLimits, DispatchError> {1399 let limits = old_limit;1400 limit_default!(old_limit, new_limit,1401 account_token_ownership_limit => ensure!(1402 new_limit <= MAX_TOKEN_OWNERSHIP,1403 <Error<T>>::CollectionLimitBoundsExceeded,1404 ),1405 sponsored_data_size => ensure!(1406 new_limit <= CUSTOM_DATA_LIMIT,1407 <Error<T>>::CollectionLimitBoundsExceeded,1408 ),14091410 sponsored_data_rate_limit => {},1411 token_limit => ensure!(1412 old_limit >= new_limit && new_limit > 0,1413 <Error<T>>::CollectionTokenLimitExceeded1414 ),14151416 sponsor_transfer_timeout(match mode {1417 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1418 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1419 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1420 }) => ensure!(1421 new_limit <= MAX_SPONSOR_TIMEOUT,1422 <Error<T>>::CollectionLimitBoundsExceeded,1423 ),1424 sponsor_approve_timeout => {},1425 owner_can_transfer => ensure!(1426 !limits.owner_can_transfer_instaled() ||1427 old_limit || !new_limit,1428 <Error<T>>::OwnerPermissionsCantBeReverted,1429 ),1430 owner_can_destroy => ensure!(1431 old_limit || !new_limit,1432 <Error<T>>::OwnerPermissionsCantBeReverted,1433 ),1434 transfers_enabled => {},1435 );1436 Ok(new_limit)1437 }14381439 1440 pub fn clamp_permissions(1441 _mode: CollectionMode,1442 old_permission: &CollectionPermissions,1443 mut new_permission: CollectionPermissions,1444 ) -> Result<CollectionPermissions, DispatchError> {1445 limit_default_clone!(old_permission, new_permission,1446 access => {},1447 mint_mode => {},1448 nesting => { },1449 );1450 Ok(new_permission)1451 }1452}145314541455#[macro_export]1456macro_rules! unsupported {1457 ($runtime:path) => {1458 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1459 };1460}146114621463pub trait CommonWeightInfo<CrossAccountId> {1464 1465 fn create_item() -> Weight;14661467 1468 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14691470 1471 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14721473 1474 fn burn_item() -> Weight;14751476 1477 1478 1479 fn set_collection_properties(amount: u32) -> Weight;14801481 1482 1483 1484 fn delete_collection_properties(amount: u32) -> Weight;14851486 1487 1488 1489 fn set_token_properties(amount: u32) -> Weight;14901491 1492 1493 1494 fn delete_token_properties(amount: u32) -> Weight;14951496 1497 1498 1499 fn set_token_property_permissions(amount: u32) -> Weight;15001501 1502 fn transfer() -> Weight;15031504 1505 fn approve() -> Weight;15061507 1508 fn transfer_from() -> Weight;15091510 1511 fn burn_from() -> Weight;15121513 1514 1515 1516 1517 fn burn_recursively_self_raw() -> Weight;15181519 1520 1521 1522 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15231524 1525 1526 1527 1528 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1529 Self::burn_recursively_self_raw()1530 .saturating_mul(max_selfs.max(1) as u64)1531 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1532 }15331534 1535 fn token_owner() -> Weight;15361537 1538 fn set_allowance_for_all() -> Weight;1539}154015411542pub trait RefungibleExtensionsWeightInfo {1543 1544 fn repartition() -> Weight;1545}154615471548154915501551pub trait CommonCollectionOperations<T: Config> {1552 1553 1554 1555 1556 1557 1558 fn create_item(1559 &self,1560 sender: T::CrossAccountId,1561 to: T::CrossAccountId,1562 data: CreateItemData,1563 nesting_budget: &dyn Budget,1564 ) -> DispatchResultWithPostInfo;15651566 1567 1568 1569 1570 1571 1572 fn create_multiple_items(1573 &self,1574 sender: T::CrossAccountId,1575 to: T::CrossAccountId,1576 data: Vec<CreateItemData>,1577 nesting_budget: &dyn Budget,1578 ) -> DispatchResultWithPostInfo;15791580 1581 1582 1583 1584 1585 1586 fn create_multiple_items_ex(1587 &self,1588 sender: T::CrossAccountId,1589 data: CreateItemExData<T::CrossAccountId>,1590 nesting_budget: &dyn Budget,1591 ) -> DispatchResultWithPostInfo;15921593 1594 1595 1596 1597 1598 fn burn_item(1599 &self,1600 sender: T::CrossAccountId,1601 token: TokenId,1602 amount: u128,1603 ) -> DispatchResultWithPostInfo;16041605 1606 1607 1608 1609 1610 1611 fn burn_item_recursively(1612 &self,1613 sender: T::CrossAccountId,1614 token: TokenId,1615 self_budget: &dyn Budget,1616 breadth_budget: &dyn Budget,1617 ) -> DispatchResultWithPostInfo;16181619 1620 1621 1622 1623 fn set_collection_properties(1624 &self,1625 sender: T::CrossAccountId,1626 properties: Vec<Property>,1627 ) -> DispatchResultWithPostInfo;16281629 1630 1631 1632 1633 fn delete_collection_properties(1634 &self,1635 sender: &T::CrossAccountId,1636 property_keys: Vec<PropertyKey>,1637 ) -> DispatchResultWithPostInfo;16381639 1640 1641 1642 1643 1644 1645 1646 1647 1648 fn set_token_properties(1649 &self,1650 sender: T::CrossAccountId,1651 token_id: TokenId,1652 properties: Vec<Property>,1653 budget: &dyn Budget,1654 ) -> DispatchResultWithPostInfo;16551656 1657 1658 1659 1660 1661 1662 1663 1664 1665 fn delete_token_properties(1666 &self,1667 sender: T::CrossAccountId,1668 token_id: TokenId,1669 property_keys: Vec<PropertyKey>,1670 budget: &dyn Budget,1671 ) -> DispatchResultWithPostInfo;16721673 1674 1675 1676 1677 1678 1679 fn set_token_property_permissions(1680 &self,1681 sender: &T::CrossAccountId,1682 property_permissions: Vec<PropertyKeyPermission>,1683 ) -> DispatchResultWithPostInfo;16841685 1686 1687 1688 1689 1690 1691 1692 fn transfer(1693 &self,1694 sender: T::CrossAccountId,1695 to: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 budget: &dyn Budget,1699 ) -> DispatchResultWithPostInfo;17001701 1702 1703 1704 1705 1706 1707 fn approve(1708 &self,1709 sender: T::CrossAccountId,1710 spender: T::CrossAccountId,1711 token: TokenId,1712 amount: u128,1713 ) -> DispatchResultWithPostInfo;17141715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 fn transfer_from(1726 &self,1727 sender: T::CrossAccountId,1728 from: T::CrossAccountId,1729 to: T::CrossAccountId,1730 token: TokenId,1731 amount: u128,1732 budget: &dyn Budget,1733 ) -> DispatchResultWithPostInfo;17341735 1736 1737 1738 1739 1740 1741 1742 1743 1744 fn burn_from(1745 &self,1746 sender: T::CrossAccountId,1747 from: T::CrossAccountId,1748 token: TokenId,1749 amount: u128,1750 budget: &dyn Budget,1751 ) -> DispatchResultWithPostInfo;17521753 1754 1755 1756 1757 1758 1759 fn check_nesting(1760 &self,1761 sender: T::CrossAccountId,1762 from: (CollectionId, TokenId),1763 under: TokenId,1764 budget: &dyn Budget,1765 ) -> DispatchResult;17661767 1768 1769 1770 1771 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17721773 1774 1775 1776 1777 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17781779 1780 1781 1782 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17831784 1785 fn collection_tokens(&self) -> Vec<TokenId>;17861787 1788 1789 1790 fn token_exists(&self, token: TokenId) -> bool;17911792 1793 fn last_token_id(&self) -> TokenId;17941795 1796 1797 1798 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17991800 1801 1802 1803 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;18041805 1806 1807 1808 1809 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;18101811 1812 1813 1814 1815 1816 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18171818 1819 fn total_supply(&self) -> u32;18201821 1822 1823 1824 fn account_balance(&self, account: T::CrossAccountId) -> u32;18251826 1827 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18281829 1830 fn total_pieces(&self, token: TokenId) -> Option<u128>;18311832 1833 1834 1835 1836 1837 fn allowance(1838 &self,1839 sender: T::CrossAccountId,1840 spender: T::CrossAccountId,1841 token: TokenId,1842 ) -> u128;18431844 1845 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;18461847 1848 1849 1850 1851 fn set_allowance_for_all(1852 &self,1853 owner: T::CrossAccountId,1854 operator: T::CrossAccountId,1855 approve: bool,1856 ) -> DispatchResultWithPostInfo;18571858 1859 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;1860}186118621863pub trait RefungibleExtensions<T>1864where1865 T: Config,1866{1867 1868 1869 1870 1871 1872 1873 1874 fn repartition(1875 &self,1876 sender: &T::CrossAccountId,1877 token: TokenId,1878 amount: u128,1879 ) -> DispatchResultWithPostInfo;1880}18811882188318841885pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1886 let post_info = PostDispatchInfo {1887 actual_weight: Some(weight),1888 pays_fee: Pays::Yes,1889 };1890 match res {1891 Ok(()) => Ok(post_info),1892 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1893 }1894}18951896impl<T: Config> From<PropertiesError> for Error<T> {1897 fn from(error: PropertiesError) -> Self {1898 match error {1899 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1900 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1901 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1902 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1903 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1904 }1905 }1906}