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 type ContractAddress: Get<H160>;380381 382 type EvmTokenAddressMapping: TokenAddressMapping<H160>;383384 385 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;386 }387388 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);389390 #[pallet::pallet]391 #[pallet::storage_version(STORAGE_VERSION)]392 #[pallet::generate_store(pub(super) trait Store)]393 pub struct Pallet<T>(_);394395 #[pallet::extra_constants]396 impl<T: Config> Pallet<T> {397 398 pub fn collection_admins_limit() -> u32 {399 COLLECTION_ADMINS_LIMIT400 }401 }402403 #[pallet::event]404 #[pallet::generate_deposit(pub fn deposit_event)]405 pub enum Event<T: Config> {406 407 CollectionCreated(408 409 CollectionId,410 411 u8,412 413 T::AccountId,414 ),415416 417 CollectionDestroyed(418 419 CollectionId,420 ),421422 423 ItemCreated(424 425 CollectionId,426 427 TokenId,428 429 T::CrossAccountId,430 431 u128,432 ),433434 435 ItemDestroyed(436 437 CollectionId,438 439 TokenId,440 441 T::CrossAccountId,442 443 u128,444 ),445446 447 Transfer(448 449 CollectionId,450 451 TokenId,452 453 T::CrossAccountId,454 455 T::CrossAccountId,456 457 u128,458 ),459460 461 Approved(462 463 CollectionId,464 465 TokenId,466 467 T::CrossAccountId,468 469 T::CrossAccountId,470 471 u128,472 ),473474 475 CollectionPropertySet(476 477 CollectionId,478 479 PropertyKey,480 ),481482 483 CollectionPropertyDeleted(484 485 CollectionId,486 487 PropertyKey,488 ),489490 491 TokenPropertySet(492 493 CollectionId,494 495 TokenId,496 497 PropertyKey,498 ),499500 501 TokenPropertyDeleted(502 503 CollectionId,504 505 TokenId,506 507 PropertyKey,508 ),509510 511 PropertyPermissionSet(512 513 CollectionId,514 515 PropertyKey,516 ),517 }518519 #[pallet::error]520 pub enum Error<T> {521 522 CollectionNotFound,523 524 MustBeTokenOwner,525 526 NoPermission,527 528 CantDestroyNotEmptyCollection,529 530 PublicMintingNotAllowed,531 532 AddressNotInAllowlist,533534 535 CollectionNameLimitExceeded,536 537 CollectionDescriptionLimitExceeded,538 539 CollectionTokenPrefixLimitExceeded,540 541 TotalCollectionsLimitExceeded,542 543 CollectionAdminCountExceeded,544 545 CollectionLimitBoundsExceeded,546 547 OwnerPermissionsCantBeReverted,548 549 TransferNotAllowed,550 551 AccountTokenLimitExceeded,552 553 CollectionTokenLimitExceeded,554 555 MetadataFlagFrozen,556557 558 TokenNotFound,559 560 TokenValueTooLow,561 562 ApprovedValueTooLow,563 564 CantApproveMoreThanOwned,565566 567 AddressIsZero,568569 570 UnsupportedOperation,571572 573 NotSufficientFounds,574575 576 UserIsNotAllowedToNest,577 578 SourceCollectionIsNotAllowedToNest,579580 581 CollectionFieldSizeExceeded,582583 584 NoSpaceForProperty,585586 587 PropertyLimitReached,588589 590 PropertyKeyIsTooLong,591592 593 InvalidCharacterInPropertyKey,594595 596 EmptyPropertyKey,597598 599 CollectionIsExternal,600601 602 CollectionIsInternal,603 }604605 606 #[pallet::storage]607 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;608609 610 #[pallet::storage]611 pub type DestroyedCollectionCount<T> =612 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;613614 615 #[pallet::storage]616 pub type CollectionById<T> = StorageMap<617 Hasher = Blake2_128Concat,618 Key = CollectionId,619 Value = Collection<<T as frame_system::Config>::AccountId>,620 QueryKind = OptionQuery,621 >;622623 624 #[pallet::storage]625 #[pallet::getter(fn collection_properties)]626 pub type CollectionProperties<T> = StorageMap<627 Hasher = Blake2_128Concat,628 Key = CollectionId,629 Value = Properties,630 QueryKind = ValueQuery,631 OnEmpty = up_data_structs::CollectionProperties,632 >;633634 635 #[pallet::storage]636 #[pallet::getter(fn property_permissions)]637 pub type CollectionPropertyPermissions<T> = StorageMap<638 Hasher = Blake2_128Concat,639 Key = CollectionId,640 Value = PropertiesPermissionMap,641 QueryKind = ValueQuery,642 >;643644 645 #[pallet::storage]646 pub type AdminAmount<T> = StorageMap<647 Hasher = Blake2_128Concat,648 Key = CollectionId,649 Value = u32,650 QueryKind = ValueQuery,651 >;652653 654 #[pallet::storage]655 pub type IsAdmin<T: Config> = StorageNMap<656 Key = (657 Key<Blake2_128Concat, CollectionId>,658 Key<Blake2_128Concat, T::CrossAccountId>,659 ),660 Value = bool,661 QueryKind = ValueQuery,662 >;663664 665 #[pallet::storage]666 pub type Allowlist<T: Config> = StorageNMap<667 Key = (668 Key<Blake2_128Concat, CollectionId>,669 Key<Blake2_128Concat, T::CrossAccountId>,670 ),671 Value = bool,672 QueryKind = ValueQuery,673 >;674675 676 #[pallet::storage]677 pub type DummyStorageValue<T: Config> = StorageValue<678 Value = (679 CollectionStats,680 CollectionId,681 TokenId,682 TokenChild,683 PhantomType<(684 TokenData<T::CrossAccountId>,685 RpcCollection<T::AccountId>,686 687 RmrkCollectionInfo<T::AccountId>,688 RmrkInstanceInfo<T::AccountId>,689 RmrkResourceInfo,690 RmrkPropertyInfo,691 RmrkBaseInfo<T::AccountId>,692 RmrkPartType,693 RmrkBoundedTheme,694 RmrkNftChild,695 )>,696 ),697 QueryKind = OptionQuery,698 >;699700 #[pallet::hooks]701 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {702 fn on_runtime_upgrade() -> Weight {703 StorageVersion::new(1).put::<Pallet<T>>();704705 Weight::zero()706 }707 }708}709710impl<T: Config> Pallet<T> {711 712 713 714 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {715 ensure!(716 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,717 <Error<T>>::AddressIsZero718 );719 Ok(())720 }721722 723 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {724 <IsAdmin<T>>::iter_prefix((collection,))725 .map(|(a, _)| a)726 .collect()727 }728729 730 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {731 <Allowlist<T>>::iter_prefix((collection,))732 .map(|(a, _)| a)733 .collect()734 }735736 737 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {738 <Allowlist<T>>::get((collection, user))739 }740741 742 pub fn collection_stats() -> CollectionStats {743 let created = <CreatedCollectionCount<T>>::get();744 let destroyed = <DestroyedCollectionCount<T>>::get();745 CollectionStats {746 created: created.0,747 destroyed: destroyed.0,748 alive: created.0 - destroyed.0,749 }750 }751752 753 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {754 let collection = <CollectionById<T>>::get(collection)?;755 let limits = collection.limits;756 let effective_limits = CollectionLimits {757 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),758 sponsored_data_size: Some(limits.sponsored_data_size()),759 sponsored_data_rate_limit: Some(760 limits761 .sponsored_data_rate_limit762 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),763 ),764 token_limit: Some(limits.token_limit()),765 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(766 match collection.mode {767 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,768 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 },771 )),772 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),773 owner_can_transfer: Some(limits.owner_can_transfer()),774 owner_can_destroy: Some(limits.owner_can_destroy()),775 transfers_enabled: Some(limits.transfers_enabled()),776 };777778 Some(effective_limits)779 }780781 782 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {783 let Collection {784 name,785 description,786 owner,787 mode,788 token_prefix,789 sponsorship,790 limits,791 permissions,792 flags,793 } = <CollectionById<T>>::get(collection)?;794795 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)796 .into_iter()797 .map(|(key, permission)| PropertyKeyPermission { key, permission })798 .collect();799800 let properties = <CollectionProperties<T>>::get(collection)801 .into_iter()802 .map(|(key, value)| Property { key, value })803 .collect();804805 let permissions = CollectionPermissions {806 access: Some(permissions.access()),807 mint_mode: Some(permissions.mint_mode()),808 nesting: Some(permissions.nesting().clone()),809 };810811 Some(RpcCollection {812 name: name.into_inner(),813 description: description.into_inner(),814 owner,815 mode,816 token_prefix: token_prefix.into_inner(),817 sponsorship,818 limits,819 permissions,820 token_property_permissions,821 properties,822 read_only: flags.external,823824 flags: RpcCollectionFlags {825 foreign: flags.foreign,826 erc721metadata: flags.erc721metadata,827 },828 })829 }830}831832macro_rules! limit_default {833 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834 $(835 if let Some($new) = $new.$field {836 let $old = $old.$field($($arg)?);837 let _ = $new;838 let _ = $old;839 $check840 } else {841 $new.$field = $old.$field842 }843 )*844 }};845}846macro_rules! limit_default_clone {847 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848 $(849 if let Some($new) = $new.$field.clone() {850 let $old = $old.$field($($arg)?);851 let _ = $new;852 let _ = $old;853 $check854 } else {855 $new.$field = $old.$field.clone()856 }857 )*858 }};859}860861impl<T: Config> Pallet<T> {862 863 864 865 866 867 pub fn init_collection(868 owner: T::CrossAccountId,869 payer: T::CrossAccountId,870 data: CreateCollectionData<T::AccountId>,871 flags: CollectionFlags,872 ) -> Result<CollectionId, DispatchError> {873 {874 ensure!(875 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,876 Error::<T>::CollectionTokenPrefixLimitExceeded877 );878 }879880 let created_count = <CreatedCollectionCount<T>>::get()881 .0882 .checked_add(1)883 .ok_or(ArithmeticError::Overflow)?;884 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;885 let id = CollectionId(created_count);886887 888 ensure!(889 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,890 <Error<T>>::TotalCollectionsLimitExceeded891 );892893 894895 let collection = Collection {896 owner: owner.as_sub().clone(),897 name: data.name,898 mode: data.mode.clone(),899 description: data.description,900 token_prefix: data.token_prefix,901 sponsorship: data902 .pending_sponsor903 .map(SponsorshipState::Unconfirmed)904 .unwrap_or_default(),905 limits: data906 .limits907 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))908 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,909 permissions: data910 .permissions911 .map(|permissions| {912 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)913 })914 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,915 flags,916 };917918 let mut collection_properties = up_data_structs::CollectionProperties::get();919 collection_properties920 .try_set_from_iter(data.properties.into_iter())921 .map_err(<Error<T>>::from)?;922923 CollectionProperties::<T>::insert(id, collection_properties);924925 let mut token_props_permissions = PropertiesPermissionMap::new();926 token_props_permissions927 .try_set_from_iter(data.token_property_permissions.into_iter())928 .map_err(<Error<T>>::from)?;929930 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);931932 933 {934 let mut imbalance =935 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();936 imbalance.subsume(937 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(938 &T::TreasuryAccountId::get(),939 T::CollectionCreationPrice::get(),940 ),941 );942 <T as Config>::Currency::settle(943 payer.as_sub(),944 imbalance,945 WithdrawReasons::TRANSFER,946 ExistenceRequirement::KeepAlive,947 )948 .map_err(|_| Error::<T>::NotSufficientFounds)?;949 }950951 <CreatedCollectionCount<T>>::put(created_count);952 <Pallet<T>>::deposit_event(Event::CollectionCreated(953 id,954 data.mode.id(),955 owner.as_sub().clone(),956 ));957 <PalletEvm<T>>::deposit_log(958 erc::CollectionHelpersEvents::CollectionCreated {959 owner: *owner.as_eth(),960 collection_id: eth::collection_id_to_address(id),961 }962 .to_log(T::ContractAddress::get()),963 );964 <CollectionById<T>>::insert(id, collection);965 Ok(id)966 }967968 969 970 971 972 pub fn destroy_collection(973 collection: CollectionHandle<T>,974 sender: &T::CrossAccountId,975 ) -> DispatchResult {976 ensure!(977 collection.limits.owner_can_destroy(),978 <Error<T>>::NoPermission,979 );980 collection.check_is_owner(sender)?;981982 let destroyed_collections = <DestroyedCollectionCount<T>>::get()983 .0984 .checked_add(1)985 .ok_or(ArithmeticError::Overflow)?;986987 988989 <DestroyedCollectionCount<T>>::put(destroyed_collections);990 <CollectionById<T>>::remove(collection.id);991 <AdminAmount<T>>::remove(collection.id);992 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);993 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);994 <CollectionProperties<T>>::remove(collection.id);995996 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));997998 <PalletEvm<T>>::deposit_log(999 erc::CollectionHelpersEvents::CollectionDestroyed {1000 collection_id: eth::collection_id_to_address(collection.id),1001 }1002 .to_log(T::ContractAddress::get()),1003 );1004 Ok(())1005 }10061007 1008 1009 1010 1011 1012 pub fn set_collection_property(1013 collection: &CollectionHandle<T>,1014 sender: &T::CrossAccountId,1015 property: Property,1016 ) -> DispatchResult {1017 collection.check_is_owner_or_admin(sender)?;10181019 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1020 let property = property.clone();1021 properties.try_set(property.key, property.value)1022 })1023 .map_err(<Error<T>>::from)?;10241025 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10261027 Ok(())1028 }10291030 1031 1032 1033 1034 1035 pub fn set_scoped_collection_property(1036 collection_id: CollectionId,1037 scope: PropertyScope,1038 property: Property,1039 ) -> DispatchResult {1040 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1041 properties.try_scoped_set(scope, property.key, property.value)1042 })1043 .map_err(<Error<T>>::from)?;10441045 Ok(())1046 }10471048 1049 1050 1051 1052 1053 pub fn set_scoped_collection_properties(1054 collection_id: CollectionId,1055 scope: PropertyScope,1056 properties: impl Iterator<Item = Property>,1057 ) -> DispatchResult {1058 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1059 stored_properties.try_scoped_set_from_iter(scope, properties)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Ok(())1064 }10651066 1067 1068 1069 1070 1071 #[transactional]1072 pub fn set_collection_properties(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 properties: Vec<Property>,1076 ) -> DispatchResult {1077 for property in properties {1078 Self::set_collection_property(collection, sender, property)?;1079 }10801081 Ok(())1082 }10831084 1085 1086 1087 1088 1089 pub fn delete_collection_property(1090 collection: &CollectionHandle<T>,1091 sender: &T::CrossAccountId,1092 property_key: PropertyKey,1093 ) -> DispatchResult {1094 collection.check_is_owner_or_admin(sender)?;10951096 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1097 properties.remove(&property_key)1098 })1099 .map_err(<Error<T>>::from)?;11001101 Self::deposit_event(Event::CollectionPropertyDeleted(1102 collection.id,1103 property_key,1104 ));11051106 Ok(())1107 }11081109 1110 1111 1112 1113 1114 #[transactional]1115 pub fn delete_collection_properties(1116 collection: &CollectionHandle<T>,1117 sender: &T::CrossAccountId,1118 property_keys: Vec<PropertyKey>,1119 ) -> DispatchResult {1120 for key in property_keys {1121 Self::delete_collection_property(collection, sender, key)?;1122 }11231124 Ok(())1125 }11261127 1128 1129 1130 1131 1132 1133 pub fn set_property_permission_unchecked(1134 collection: CollectionId,1135 property_permission: PropertyKeyPermission,1136 ) -> DispatchResult {1137 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1138 permissions.try_set(property_permission.key, property_permission.permission)1139 })1140 .map_err(<Error<T>>::from)?;1141 Ok(())1142 }11431144 1145 1146 1147 1148 1149 pub fn set_property_permission(1150 collection: &CollectionHandle<T>,1151 sender: &T::CrossAccountId,1152 property_permission: PropertyKeyPermission,1153 ) -> DispatchResult {1154 Self::set_scoped_property_permission(1155 collection,1156 sender,1157 PropertyScope::None,1158 property_permission,1159 )1160 }11611162 1163 1164 1165 1166 1167 1168 pub fn set_scoped_property_permission(1169 collection: &CollectionHandle<T>,1170 sender: &T::CrossAccountId,1171 scope: PropertyScope,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_scoped_set(1188 scope,1189 property_permission.key,1190 property_permission.permission,1191 )1192 })1193 .map_err(<Error<T>>::from)?;11941195 Self::deposit_event(Event::PropertyPermissionSet(1196 collection.id,1197 property_permission.key,1198 ));11991200 Ok(())1201 }12021203 1204 1205 1206 1207 1208 #[transactional]1209 pub fn set_token_property_permissions(1210 collection: &CollectionHandle<T>,1211 sender: &T::CrossAccountId,1212 property_permissions: Vec<PropertyKeyPermission>,1213 ) -> DispatchResult {1214 Self::set_scoped_token_property_permissions(1215 collection,1216 sender,1217 PropertyScope::None,1218 property_permissions,1219 )1220 }12211222 1223 1224 1225 1226 1227 1228 #[transactional]1229 pub fn set_scoped_token_property_permissions(1230 collection: &CollectionHandle<T>,1231 sender: &T::CrossAccountId,1232 scope: PropertyScope,1233 property_permissions: Vec<PropertyKeyPermission>,1234 ) -> DispatchResult {1235 for prop_pemission in property_permissions {1236 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1237 }12381239 Ok(())1240 }12411242 1243 pub fn get_collection_property(1244 collection_id: CollectionId,1245 key: &PropertyKey,1246 ) -> Option<PropertyValue> {1247 Self::collection_properties(collection_id).get(key).cloned()1248 }12491250 1251 pub fn bytes_keys_to_property_keys(1252 keys: Vec<Vec<u8>>,1253 ) -> Result<Vec<PropertyKey>, DispatchError> {1254 keys.into_iter()1255 .map(|key| -> Result<PropertyKey, DispatchError> {1256 key.try_into()1257 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1258 })1259 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1260 }12611262 1263 pub fn filter_collection_properties(1264 collection_id: CollectionId,1265 keys: Option<Vec<PropertyKey>>,1266 ) -> Result<Vec<Property>, DispatchError> {1267 let properties = Self::collection_properties(collection_id);12681269 let properties = keys1270 .map(|keys| {1271 keys.into_iter()1272 .filter_map(|key| {1273 properties.get(&key).map(|value| Property {1274 key,1275 value: value.clone(),1276 })1277 })1278 .collect()1279 })1280 .unwrap_or_else(|| {1281 properties1282 .into_iter()1283 .map(|(key, value)| Property { key, value })1284 .collect()1285 });12861287 Ok(properties)1288 }12891290 1291 pub fn filter_property_permissions(1292 collection_id: CollectionId,1293 keys: Option<Vec<PropertyKey>>,1294 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1295 let permissions = Self::property_permissions(collection_id);12961297 let key_permissions = keys1298 .map(|keys| {1299 keys.into_iter()1300 .filter_map(|key| {1301 permissions1302 .get(&key)1303 .map(|permission| PropertyKeyPermission {1304 key,1305 permission: permission.clone(),1306 })1307 })1308 .collect()1309 })1310 .unwrap_or_else(|| {1311 permissions1312 .into_iter()1313 .map(|(key, permission)| PropertyKeyPermission { key, permission })1314 .collect()1315 });13161317 Ok(key_permissions)1318 }13191320 1321 1322 1323 pub fn toggle_allowlist(1324 collection: &CollectionHandle<T>,1325 sender: &T::CrossAccountId,1326 user: &T::CrossAccountId,1327 allowed: bool,1328 ) -> DispatchResult {1329 collection.check_is_owner_or_admin(sender)?;13301331 13321333 if allowed {1334 <Allowlist<T>>::insert((collection.id, user), true);1335 } else {1336 <Allowlist<T>>::remove((collection.id, user));1337 }13381339 Ok(())1340 }13411342 1343 1344 1345 pub fn toggle_admin(1346 collection: &CollectionHandle<T>,1347 sender: &T::CrossAccountId,1348 user: &T::CrossAccountId,1349 admin: bool,1350 ) -> DispatchResult {1351 collection.check_is_owner(sender)?;13521353 let was_admin = <IsAdmin<T>>::get((collection.id, user));1354 if was_admin == admin {1355 return Ok(());1356 }1357 let amount = <AdminAmount<T>>::get(collection.id);13581359 if admin {1360 let amount = amount1361 .checked_add(1)1362 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1363 ensure!(1364 amount <= Self::collection_admins_limit(),1365 <Error<T>>::CollectionAdminCountExceeded,1366 );13671368 13691370 <AdminAmount<T>>::insert(collection.id, amount);1371 <IsAdmin<T>>::insert((collection.id, user), true);1372 } else {1373 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1374 <IsAdmin<T>>::remove((collection.id, user));1375 }13761377 Ok(())1378 }13791380 1381 pub fn clamp_limits(1382 mode: CollectionMode,1383 old_limit: &CollectionLimits,1384 mut new_limit: CollectionLimits,1385 ) -> Result<CollectionLimits, DispatchError> {1386 let limits = old_limit;1387 limit_default!(old_limit, new_limit,1388 account_token_ownership_limit => ensure!(1389 new_limit <= MAX_TOKEN_OWNERSHIP,1390 <Error<T>>::CollectionLimitBoundsExceeded,1391 ),1392 sponsored_data_size => ensure!(1393 new_limit <= CUSTOM_DATA_LIMIT,1394 <Error<T>>::CollectionLimitBoundsExceeded,1395 ),13961397 sponsored_data_rate_limit => {},1398 token_limit => ensure!(1399 old_limit >= new_limit && new_limit > 0,1400 <Error<T>>::CollectionTokenLimitExceeded1401 ),14021403 sponsor_transfer_timeout(match mode {1404 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1405 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 }) => ensure!(1408 new_limit <= MAX_SPONSOR_TIMEOUT,1409 <Error<T>>::CollectionLimitBoundsExceeded,1410 ),1411 sponsor_approve_timeout => {},1412 owner_can_transfer => ensure!(1413 !limits.owner_can_transfer_instaled() ||1414 old_limit || !new_limit,1415 <Error<T>>::OwnerPermissionsCantBeReverted,1416 ),1417 owner_can_destroy => ensure!(1418 old_limit || !new_limit,1419 <Error<T>>::OwnerPermissionsCantBeReverted,1420 ),1421 transfers_enabled => {},1422 );1423 Ok(new_limit)1424 }14251426 1427 pub fn clamp_permissions(1428 _mode: CollectionMode,1429 old_permission: &CollectionPermissions,1430 mut new_permission: CollectionPermissions,1431 ) -> Result<CollectionPermissions, DispatchError> {1432 limit_default_clone!(old_permission, new_permission,1433 access => {},1434 mint_mode => {},1435 nesting => { },1436 );1437 Ok(new_permission)1438 }1439}144014411442#[macro_export]1443macro_rules! unsupported {1444 ($runtime:path) => {1445 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1446 };1447}144814491450pub trait CommonWeightInfo<CrossAccountId> {1451 1452 fn create_item() -> Weight;14531454 1455 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14561457 1458 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14591460 1461 fn burn_item() -> Weight;14621463 1464 1465 1466 fn set_collection_properties(amount: u32) -> Weight;14671468 1469 1470 1471 fn delete_collection_properties(amount: u32) -> Weight;14721473 1474 1475 1476 fn set_token_properties(amount: u32) -> Weight;14771478 1479 1480 1481 fn delete_token_properties(amount: u32) -> Weight;14821483 1484 1485 1486 fn set_token_property_permissions(amount: u32) -> Weight;14871488 1489 fn transfer() -> Weight;14901491 1492 fn approve() -> Weight;14931494 1495 fn transfer_from() -> Weight;14961497 1498 fn burn_from() -> Weight;14991500 1501 1502 1503 1504 fn burn_recursively_self_raw() -> Weight;15051506 1507 1508 1509 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15101511 1512 1513 1514 1515 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1516 Self::burn_recursively_self_raw()1517 .saturating_mul(max_selfs.max(1) as u64)1518 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1519 }15201521 1522 fn token_owner() -> Weight;1523}152415251526pub trait RefungibleExtensionsWeightInfo {1527 1528 fn repartition() -> Weight;1529}153015311532153315341535pub trait CommonCollectionOperations<T: Config> {1536 1537 1538 1539 1540 1541 1542 fn create_item(1543 &self,1544 sender: T::CrossAccountId,1545 to: T::CrossAccountId,1546 data: CreateItemData,1547 nesting_budget: &dyn Budget,1548 ) -> DispatchResultWithPostInfo;15491550 1551 1552 1553 1554 1555 1556 fn create_multiple_items(1557 &self,1558 sender: T::CrossAccountId,1559 to: T::CrossAccountId,1560 data: Vec<CreateItemData>,1561 nesting_budget: &dyn Budget,1562 ) -> DispatchResultWithPostInfo;15631564 1565 1566 1567 1568 1569 1570 fn create_multiple_items_ex(1571 &self,1572 sender: T::CrossAccountId,1573 data: CreateItemExData<T::CrossAccountId>,1574 nesting_budget: &dyn Budget,1575 ) -> DispatchResultWithPostInfo;15761577 1578 1579 1580 1581 1582 fn burn_item(1583 &self,1584 sender: T::CrossAccountId,1585 token: TokenId,1586 amount: u128,1587 ) -> DispatchResultWithPostInfo;15881589 1590 1591 1592 1593 1594 1595 fn burn_item_recursively(1596 &self,1597 sender: T::CrossAccountId,1598 token: TokenId,1599 self_budget: &dyn Budget,1600 breadth_budget: &dyn Budget,1601 ) -> DispatchResultWithPostInfo;16021603 1604 1605 1606 1607 fn set_collection_properties(1608 &self,1609 sender: T::CrossAccountId,1610 properties: Vec<Property>,1611 ) -> DispatchResultWithPostInfo;16121613 1614 1615 1616 1617 fn delete_collection_properties(1618 &self,1619 sender: &T::CrossAccountId,1620 property_keys: Vec<PropertyKey>,1621 ) -> DispatchResultWithPostInfo;16221623 1624 1625 1626 1627 1628 1629 1630 1631 1632 fn set_token_properties(1633 &self,1634 sender: T::CrossAccountId,1635 token_id: TokenId,1636 properties: Vec<Property>,1637 budget: &dyn Budget,1638 ) -> DispatchResultWithPostInfo;16391640 1641 1642 1643 1644 1645 1646 1647 1648 1649 fn delete_token_properties(1650 &self,1651 sender: T::CrossAccountId,1652 token_id: TokenId,1653 property_keys: Vec<PropertyKey>,1654 budget: &dyn Budget,1655 ) -> DispatchResultWithPostInfo;16561657 1658 1659 1660 1661 1662 1663 fn set_token_property_permissions(1664 &self,1665 sender: &T::CrossAccountId,1666 property_permissions: Vec<PropertyKeyPermission>,1667 ) -> DispatchResultWithPostInfo;16681669 1670 1671 1672 1673 1674 1675 1676 fn transfer(1677 &self,1678 sender: T::CrossAccountId,1679 to: T::CrossAccountId,1680 token: TokenId,1681 amount: u128,1682 budget: &dyn Budget,1683 ) -> DispatchResultWithPostInfo;16841685 1686 1687 1688 1689 1690 1691 fn approve(1692 &self,1693 sender: T::CrossAccountId,1694 spender: T::CrossAccountId,1695 token: TokenId,1696 amount: u128,1697 ) -> DispatchResultWithPostInfo;16981699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 fn transfer_from(1710 &self,1711 sender: T::CrossAccountId,1712 from: T::CrossAccountId,1713 to: T::CrossAccountId,1714 token: TokenId,1715 amount: u128,1716 budget: &dyn Budget,1717 ) -> DispatchResultWithPostInfo;17181719 1720 1721 1722 1723 1724 1725 1726 1727 1728 fn burn_from(1729 &self,1730 sender: T::CrossAccountId,1731 from: T::CrossAccountId,1732 token: TokenId,1733 amount: u128,1734 budget: &dyn Budget,1735 ) -> DispatchResultWithPostInfo;17361737 1738 1739 1740 1741 1742 1743 fn check_nesting(1744 &self,1745 sender: T::CrossAccountId,1746 from: (CollectionId, TokenId),1747 under: TokenId,1748 budget: &dyn Budget,1749 ) -> DispatchResult;17501751 1752 1753 1754 1755 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17561757 1758 1759 1760 1761 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17621763 1764 1765 1766 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17671768 1769 fn collection_tokens(&self) -> Vec<TokenId>;17701771 1772 1773 1774 fn token_exists(&self, token: TokenId) -> bool;17751776 1777 fn last_token_id(&self) -> TokenId;17781779 1780 1781 1782 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17831784 1785 1786 1787 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17881789 1790 1791 1792 1793 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17941795 1796 1797 1798 1799 1800 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18011802 1803 fn total_supply(&self) -> u32;18041805 1806 1807 1808 fn account_balance(&self, account: T::CrossAccountId) -> u32;18091810 1811 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18121813 1814 fn total_pieces(&self, token: TokenId) -> Option<u128>;18151816 1817 1818 1819 1820 1821 fn allowance(1822 &self,1823 sender: T::CrossAccountId,1824 spender: T::CrossAccountId,1825 token: TokenId,1826 ) -> u128;18271828 1829 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1830}183118321833pub trait RefungibleExtensions<T>1834where1835 T: Config,1836{1837 1838 1839 1840 1841 1842 1843 1844 fn repartition(1845 &self,1846 sender: &T::CrossAccountId,1847 token: TokenId,1848 amount: u128,1849 ) -> DispatchResultWithPostInfo;1850}18511852185318541855pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1856 let post_info = PostDispatchInfo {1857 actual_weight: Some(weight),1858 pays_fee: Pays::Yes,1859 };1860 match res {1861 Ok(()) => Ok(post_info),1862 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1863 }1864}18651866impl<T: Config> From<PropertiesError> for Error<T> {1867 fn from(error: PropertiesError) -> Self {1868 match error {1869 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1870 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1871 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1872 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1873 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1874 }1875 }1876}